arangojs
Version: 
The official ArangoDB JavaScript driver.
1,537 lines (1,536 loc) • 78 kB
JavaScript
/**
 * ```js
 * import { Database } from "arangojs/database.js";
 * ```
 *
 * The "database" module provides the {@link Database} class and associated
 * types and interfaces for TypeScript.
 *
 * The Database class is also re-exported by the "index" module.
 *
 * @packageDocumentation
 */
import { Analyzer, } from "./analyzer.js";
import { isAqlLiteral, isAqlQuery } from "./aql.js";
import { Collection, collectionToString, CollectionType, isArangoCollection, } from "./collection.js";
import { Connection, } from "./connection.js";
import { BatchedArrayCursor } from "./cursor.js";
import { isArangoError } from "./error.js";
import { Graph, } from "./graph.js";
import { Job } from "./job.js";
import { DATABASE_NOT_FOUND } from "./lib/codes.js";
import { Route } from "./route.js";
import { Transaction } from "./transaction.js";
import { View } from "./view.js";
/**
 * Indicates whether the given value represents a {@link Database}.
 *
 * @param database - A value that might be a database.
 */
export function isArangoDatabase(database) {
    return Boolean(database && database.isArangoDatabase);
}
/**
 * @internal
 */
function coerceTransactionCollections(collections) {
    if (typeof collections === "string") {
        return { write: [collections] };
    }
    if (Array.isArray(collections)) {
        return { write: collections.map(collectionToString) };
    }
    if (isArangoCollection(collections)) {
        return { write: collectionToString(collections) };
    }
    const cols = {};
    if (collections) {
        if (collections.allowImplicit !== undefined) {
            cols.allowImplicit = collections.allowImplicit;
        }
        if (collections.read) {
            cols.read = Array.isArray(collections.read)
                ? collections.read.map(collectionToString)
                : collectionToString(collections.read);
        }
        if (collections.write) {
            cols.write = Array.isArray(collections.write)
                ? collections.write.map(collectionToString)
                : collectionToString(collections.write);
        }
        if (collections.exclusive) {
            cols.exclusive = Array.isArray(collections.exclusive)
                ? collections.exclusive.map(collectionToString)
                : collectionToString(collections.exclusive);
        }
    }
    return cols;
}
/**
 * Numeric representation of the logging level of a log entry.
 */
export var LogLevel;
(function (LogLevel) {
    LogLevel[LogLevel["FATAL"] = 0] = "FATAL";
    LogLevel[LogLevel["ERROR"] = 1] = "ERROR";
    LogLevel[LogLevel["WARNING"] = 2] = "WARNING";
    LogLevel[LogLevel["INFO"] = 3] = "INFO";
    LogLevel[LogLevel["DEBUG"] = 4] = "DEBUG";
})(LogLevel || (LogLevel = {}));
/**
 * An object representing a single ArangoDB database. All arangojs collections,
 * cursors, analyzers and so on are linked to a `Database` object.
 */
export class Database {
    _connection;
    _name;
    _analyzers = new Map();
    _collections = new Map();
    _graphs = new Map();
    _views = new Map();
    _trapRequest;
    constructor(configOrDatabase = {}, name) {
        if (isArangoDatabase(configOrDatabase)) {
            const connection = configOrDatabase._connection;
            const databaseName = name || configOrDatabase.name;
            this._connection = connection;
            this._name = databaseName;
            const database = connection.database(databaseName);
            if (database)
                return database;
        }
        else {
            const config = configOrDatabase;
            const { databaseName, ...options } = typeof config === "string" || Array.isArray(config)
                ? { databaseName: name, url: config }
                : config;
            this._connection = new Connection(options);
            this._name = databaseName || "_system";
        }
    }
    //#region misc
    /**
     * @internal
     *
     * Indicates that this object represents an ArangoDB database.
     */
    get isArangoDatabase() {
        return true;
    }
    /**
     * Name of the ArangoDB database this instance represents.
     */
    get name() {
        return this._name;
    }
    /**
     * Fetches version information from the ArangoDB server.
     *
     * @param details - If set to `true`, additional information about the
     * ArangoDB server will be available as the `details` property.
     *
     * @example
     * ```js
     * const db = new Database();
     * const version = await db.version();
     * // the version object contains the ArangoDB version information.
     * // license: "community" or "enterprise"
     * // version: ArangoDB version number
     * // server: description of the server
     * ```
     */
    version(details) {
        return this.request({
            method: "GET",
            path: "/_api/version",
            search: { details },
        });
    }
    /**
     * Retrives the server's current system time in milliseconds with microsecond
     * precision.
     */
    time() {
        return this.request({
            path: "/_admin/time",
        }, (res) => res.parsedBody.time * 1000);
    }
    /**
     * Returns a new {@link route.Route} instance for the given path (relative to the
     * database) that can be used to perform arbitrary HTTP requests.
     *
     * @param path - The database-relative URL of the route. Defaults to the
     * database API root.
     * @param headers - Default headers that should be sent with each request to
     * the route.
     *
     * @example
     * ```js
     * const db = new Database();
     * const myFoxxService = db.route("my-foxx-service");
     * const response = await myFoxxService.post("users", {
     *   username: "admin",
     *   password: "hunter2"
     * });
     * // response.body is the result of
     * // POST /_db/_system/my-foxx-service/users
     * // with JSON request body '{"username": "admin", "password": "hunter2"}'
     * ```
     */
    route(path, headers) {
        return new Route(this, path, headers);
    }
    /**
     * Creates an async job by executing the given callback function. The first
     * database request performed by the callback will be marked for asynchronous
     * execution and its result will be made available as an async job.
     *
     * Returns a {@link Job} instance that can be used to retrieve the result
     * of the callback function once the request has been executed.
     *
     * @param callback - Callback function to execute as an async job.
     *
     * @example
     * ```js
     * const db = new Database();
     * const job = await db.createJob(() => db.collections());
     * while (!job.isLoaded) {
     *  await timeout(1000);
     *  await job.load();
     * }
     * // job.result is a list of Collection instances
     * ```
     */
    async createJob(callback) {
        const trap = new Promise((resolveTrap) => {
            this._trapRequest = (trapped) => resolveTrap(trapped);
        });
        const eventualResult = callback();
        const trapped = await trap;
        if (trapped.error)
            return eventualResult;
        const { jobId, onResolve, onReject } = trapped;
        return new Job(this, jobId, (res) => {
            onResolve(res);
            return eventualResult;
        }, (e) => {
            onReject(e);
            return eventualResult;
        });
    }
    async request({ absolutePath = false, basePath, ...opts }, transform = (res) => res.parsedBody) {
        if (!absolutePath) {
            basePath = `/_db/${encodeURIComponent(this._name)}${basePath || ""}`;
        }
        if (this._trapRequest) {
            const trap = this._trapRequest;
            this._trapRequest = undefined;
            return new Promise(async (resolveRequest, rejectRequest) => {
                const options = { ...opts };
                options.headers = new Headers(options.headers);
                options.headers.set("x-arango-async", "store");
                let jobRes;
                try {
                    jobRes = await this._connection.request({ basePath, ...options });
                }
                catch (e) {
                    trap({ error: true });
                    rejectRequest(e);
                    return;
                }
                const jobId = jobRes.headers.get("x-arango-async-id");
                trap({
                    jobId,
                    onResolve: (res) => {
                        const result = transform ? transform(res) : res;
                        resolveRequest(result);
                        return result;
                    },
                    onReject: (err) => {
                        rejectRequest(err);
                        throw err;
                    },
                });
            });
        }
        return this._connection.request({ basePath, ...opts }, transform || undefined);
    }
    /**
     * Updates the URL list by requesting a list of all coordinators in the
     * cluster and adding any endpoints not initially specified in the
     * {@link connection.Config}.
     *
     * For long-running processes communicating with an ArangoDB cluster it is
     * recommended to run this method periodically (e.g. once per hour) to make
     * sure new coordinators are picked up correctly and can be used for
     * fail-over or load balancing.
     *
     * @param overwrite - If set to `true`, the existing host list will be
     * replaced instead of extended.
     *
     * @example
     * ```js
     * const db = new Database();
     * const interval = setInterval(
     *   () => db.acquireHostList(),
     *   5 * 60 * 1000 // every 5 minutes
     * );
     *
     * // later
     * clearInterval(interval);
     * system.close();
     * ```
     */
    async acquireHostList(overwrite = false) {
        const urls = await this.request({ path: "/_api/cluster/endpoints" }, (res) => res.parsedBody.endpoints.map((endpoint) => endpoint.endpoint));
        if (urls.length > 0) {
            if (overwrite)
                this._connection.setHostList(urls);
            else
                this._connection.addToHostList(urls);
        }
    }
    /**
     * Closes all active connections of this database instance.
     *
     * Can be used to clean up idling connections during longer periods of
     * inactivity.
     *
     * **Note**: This method currently has no effect in the browser version of
     * arangojs.
     *
     * @example
     * ```js
     * const db = new Database();
     * const sessions = db.collection("sessions");
     * // Clean up expired sessions once per hour
     * setInterval(async () => {
     *   await db.query(aql`
     *     FOR session IN ${sessions}
     *     FILTER session.expires < DATE_NOW()
     *     REMOVE session IN ${sessions}
     *   `);
     *   // Making sure to close the connections because they're no longer used
     *   system.close();
     * }, 1000 * 60 * 60);
     * ```
     */
    close() {
        this._connection.close();
    }
    /**
     * Attempts to initiate a clean shutdown of the server.
     */
    shutdown() {
        return this.request({
            method: "DELETE",
            path: "/_admin/shutdown",
        }, () => undefined);
    }
    async waitForPropagation({ basePath, ...request }, timeout) {
        await this._connection.waitForPropagation({
            ...request,
            basePath: `/_db/${encodeURIComponent(this._name)}${basePath || ""}`,
        }, timeout);
    }
    /**
     * Methods for accessing the server-reported queue times of the mostly
     * recently received responses.
     */
    get queueTime() {
        return this._connection.queueTime;
    }
    /**
     * Sets the limit for the number of values of the most recently received
     * server-reported queue times that can be accessed using
     * {@link Database#queueTime}.
     *
     * @param responseQueueTimeSamples - Number of values to maintain.
     */
    setResponseQueueTimeSamples(responseQueueTimeSamples) {
        this._connection.setResponseQueueTimeSamples(responseQueueTimeSamples);
    }
    //#endregion
    //#region auth
    /**
     * Updates the underlying connection's `authorization` header to use Basic
     * authentication with the given `username` and `password`, then returns
     * itself.
     *
     * @param username - The username to authenticate with.
     * @param password - The password to authenticate with.
     *
     * @example
     * ```js
     * const db = new Database();
     * db.useBasicAuth("admin", "hunter2");
     * // with the username "admin" and password "hunter2".
     * ```
     */
    useBasicAuth(username = "root", password = "") {
        this._connection.setBasicAuth({ username, password });
        return this;
    }
    /**
     * Updates the underlying connection's `authorization` header to use Bearer
     * authentication with the given authentication `token`, then returns itself.
     *
     * @param token - The token to authenticate with.
     *
     * @example
     * ```js
     * const db = new Database();
     * db.useBearerAuth("keyboardcat");
     * // The database instance now uses Bearer authentication.
     * ```
     */
    useBearerAuth(token) {
        this._connection.setBearerAuth({ token });
        return this;
    }
    /**
     * Validates the given database credentials and exchanges them for an
     * authentication token, then uses the authentication token for future
     * requests and returns it.
     *
     * @param username - The username to authenticate with.
     * @param password - The password to authenticate with.
     *
     * @example
     * ```js
     * const db = new Database();
     * await db.login("admin", "hunter2");
     * // with an authentication token for the "admin" user.
     * ```
     */
    login(username = "root", password = "") {
        return this.request({
            method: "POST",
            path: "/_open/auth",
            body: { username, password },
        }, (res) => {
            this.useBearerAuth(res.parsedBody.jwt);
            return res.parsedBody.jwt;
        });
    }
    /**
     * Attempts to renew the authentication token passed to {@link Database#useBearerAuth}
     * or returned and used by {@link Database#login}. If a new authentication
     * token is issued, it will be used for future requests and returned.
     *
     * @example
     * ```js
     * const db = new Database();
     * await db.login("admin", "hunter2");
     * // ... later ...
     * const newToken = await db.renewAuthToken();
     * if (!newToken) // no new token issued
     * ```
     */
    renewAuthToken() {
        return this.request({
            method: "POST",
            path: "/_open/auth/renew",
        }, (res) => {
            if (!res.parsedBody.jwt)
                return null;
            this.useBearerAuth(res.parsedBody.jwt);
            return res.parsedBody.jwt;
        });
    }
    //#endregion
    //#region rebalancing
    /**
     * Computes the current cluster imbalance.
     *
     * @example
     * ```js
     * const db = new Database();
     * const imbalance = await db.getClusterImbalance();
     * ```
     */
    getClusterImbalance() {
        return this.request({ path: "/_admin/cluster/rebalance" }, (res) => res.parsedBody.result);
    }
    /**
     * Computes a set of move shard operations to rebalance the cluster.
     *
     * @example
     * ```js
     * const db = new Database();
     * const result = await db.computerClusterRebalance({
     *   moveLeaders: true,
     *   moveFollowers: true
     * });
     * if (result.moves.length) {
     *   await db.executeClusterRebalance(result.moves);
     * }
     * ```
     */
    computeClusterRebalance(opts) {
        return this.request({
            method: "POST",
            path: "/_admin/cluster/rebalance",
            body: {
                version: 1,
                ...opts,
            },
        }, (res) => res.parsedBody.result);
    }
    /**
     * Executes the given cluster move shard operations.
     *
     * @example
     * ```js
     * const db = new Database();
     * const result = await db.computerClusterRebalance({
     *   moveLeaders: true,
     *   moveFollowers: true
     * });
     * if (result.moves.length) {
     *   await db.executeClusterRebalance(result.moves);
     * }
     * ```
     */
    executeClusterRebalance(moves) {
        return this.request({
            method: "POST",
            path: "/_admin/cluster/rebalance/execute",
            body: {
                version: 1,
                moves,
            },
        });
    }
    /**
     * Computes a set of move shard operations to rebalance the cluster and
     * executes them.
     *
     * @example
     * ```js
     * const db = new Database();
     * const result = await db.rebalanceCluster({
     *   moveLeaders: true,
     *   moveFollowers: true
     * });
     * // The cluster is now rebalanced.
     * ```
     */
    rebalanceCluster(opts) {
        return this.request({
            method: "PUT",
            path: "/_admin/cluster/rebalance",
            body: {
                version: 1,
                ...opts,
            },
        });
    }
    //#endregion
    //#region databases
    /**
     * Creates a new `Database` instance for the given `databaseName` that
     * shares this database's connection pool.
     *
     * See also {@link Database:constructor}.
     *
     * @param databaseName - Name of the database.
     *
     * @example
     * ```js
     * const systemDb = new Database();
     * const myDb = system.database("my_database");
     * ```
     */
    database(databaseName) {
        return new Database(this, databaseName);
    }
    /**
     * Fetches the database description for the active database from the server.
     *
     * @example
     * ```js
     * const db = new Database();
     * const info = await db.get();
     * // the database exists
     * ```
     */
    get() {
        return this.request({ path: "/_api/database/current" }, (res) => res.parsedBody.result);
    }
    /**
     * Checks whether the database exists.
     *
     * @example
     * ```js
     * const db = new Database();
     * const result = await db.exists();
     * // result indicates whether the database exists
     * ```
     */
    async exists() {
        try {
            await this.get();
            return true;
        }
        catch (err) {
            if (isArangoError(err) && err.errorNum === DATABASE_NOT_FOUND) {
                return false;
            }
            throw err;
        }
    }
    createDatabase(databaseName, usersOrOptions = {}) {
        const { users, ...options } = Array.isArray(usersOrOptions)
            ? { users: usersOrOptions }
            : usersOrOptions;
        return this.request({
            method: "POST",
            path: "/_api/database",
            body: { name: databaseName, users, options },
        }, () => this.database(databaseName));
    }
    /**
     * Fetches all databases from the server and returns an array of their names.
     *
     * See also {@link Database#databases} and
     * {@link Database#listUserDatabases}.
     *
     * @example
     * ```js
     * const db = new Database();
     * const names = await db.listDatabases();
     * // databases is an array of database names
     * ```
     */
    listDatabases() {
        return this.request({ path: "/_api/database" }, (res) => res.parsedBody.result);
    }
    /**
     * Fetches all databases accessible to the active user from the server and
     * returns an array of their names.
     *
     * See also {@link Database#userDatabases} and
     * {@link Database#listDatabases}.
     *
     * @example
     * ```js
     * const db = new Database();
     * const names = await db.listUserDatabases();
     * // databases is an array of database names
     * ```
     */
    listUserDatabases() {
        return this.request({ path: "/_api/database/user" }, (res) => res.parsedBody.result);
    }
    /**
     * Fetches all databases from the server and returns an array of `Database`
     * instances for those databases.
     *
     * See also {@link Database#listDatabases} and
     * {@link Database#userDatabases}.
     *
     * @example
     * ```js
     * const db = new Database();
     * const names = await db.databases();
     * // databases is an array of databases
     * ```
     */
    databases() {
        return this.request({ path: "/_api/database" }, (res) => res.parsedBody.result.map((databaseName) => this.database(databaseName)));
    }
    /**
     * Fetches all databases accessible to the active user from the server and
     * returns an array of `Database` instances for those databases.
     *
     * See also {@link Database#listUserDatabases} and
     * {@link Database#databases}.
     *
     * @example
     * ```js
     * const db = new Database();
     * const names = await db.userDatabases();
     * // databases is an array of databases
     * ```
     */
    userDatabases() {
        return this.request({ path: "/_api/database/user" }, (res) => res.parsedBody.result.map((databaseName) => this.database(databaseName)));
    }
    /**
     * Deletes the database with the given `databaseName` from the server.
     *
     * @param databaseName - Name of the database to delete.
     *
     * @example
     * ```js
     * const db = new Database();
     * await db.dropDatabase("mydb");
     * // database "mydb" no longer exists
     * ```
     */
    dropDatabase(databaseName) {
        return this.request({
            method: "DELETE",
            path: `/_api/database/${encodeURIComponent(databaseName)}`,
        }, (res) => res.parsedBody.result);
    }
    //#endregion
    //#region collections
    /**
     * Returns a `Collection` instance for the given collection name.
     *
     * In TypeScript the collection implements both the
     * {@link collection.DocumentCollection} and {@link collection.EdgeCollection}
     * interfaces and can be cast to either type to enforce a stricter API.
     *
     * @param T - Type to use for document data. Defaults to `any`.
     * @param collectionName - Name of the edge collection.
     *
     * @example
     * ```js
     * const db = new Database();
     * const collection = db.collection("potatoes");
     * ```
     *
     * @example
     * ```ts
     * interface Person {
     *   name: string;
     * }
     * const db = new Database();
     * const persons = db.collection<Person>("persons");
     * ```
     *
     * @example
     * ```ts
     * interface Person {
     *   name: string;
     * }
     * interface Friend {
     *   startDate: number;
     *   endDate?: number;
     * }
     * const db = new Database();
     * const documents = db.collection("persons") as DocumentCollection<Person>;
     * const edges = db.collection("friends") as EdgeCollection<Friend>;
     * ```
     */
    collection(collectionName) {
        collectionName = collectionName;
        if (!this._collections.has(collectionName)) {
            this._collections.set(collectionName, new Collection(this, collectionName));
        }
        return this._collections.get(collectionName);
    }
    async createCollection(collectionName, options) {
        const collection = this.collection(collectionName);
        await collection.create(options);
        return collection;
    }
    /**
     * Creates a new edge collection with the given `collectionName` and
     * `options`, then returns an {@link collection.EdgeCollection} instance for the new
     * edge collection.
     *
     * This is a convenience method for calling {@link Database#createCollection}
     * with `options.type` set to `EDGE_COLLECTION`.
     *
     * @param T - Type to use for edge document data. Defaults to `any`.
     * @param collectionName - Name of the new collection.
     * @param options - Options for creating the collection.
     *
     * @example
     * ```js
     * const db = new Database();
     * const edges = db.createEdgeCollection("friends");
     * ```
     *
     * @example
     * ```ts
     * interface Friend {
     *   startDate: number;
     *   endDate?: number;
     * }
     * const db = new Database();
     * const edges = db.createEdgeCollection<Friend>("friends");
     * ```
     */
    async createEdgeCollection(collectionName, options) {
        return this.createCollection(collectionName, {
            ...options,
            type: CollectionType.EDGE_COLLECTION,
        });
    }
    /**
     * Renames the collection `collectionName` to `newName`.
     *
     * Additionally removes any stored `Collection` instance for
     * `collectionName` from the `Database` instance's internal cache.
     *
     * **Note**: Renaming collections may not be supported when ArangoDB is
     * running in a cluster configuration.
     *
     * @param collectionName - Current name of the collection.
     * @param newName - The new name of the collection.
     */
    async renameCollection(collectionName, newName) {
        const result = await this.request({
            method: "PUT",
            path: `/_api/collection/${encodeURIComponent(collectionName)}/rename`,
            body: { name: newName },
        });
        this._collections.delete(collectionName);
        return result;
    }
    /**
     * Fetches all collections from the database and returns an array of
     * collection descriptions.
     *
     * See also {@link Database#collections}.
     *
     * @param excludeSystem - Whether system collections should be excluded.
     *
     * @example
     * ```js
     * const db = new Database();
     * const collections = await db.listCollections();
     * // collections is an array of collection descriptions
     * // not including system collections
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * const collections = await db.listCollections(false);
     * // collections is an array of collection descriptions
     * // including system collections
     * ```
     */
    listCollections(excludeSystem = true) {
        return this.request({
            path: "/_api/collection",
            search: { excludeSystem },
        }, (res) => res.parsedBody.result);
    }
    /**
     * Fetches all collections from the database and returns an array of
     * `Collection` instances.
     *
     * In TypeScript these instances implement both the
     * {@link collection.DocumentCollection} and {@link collection.EdgeCollection}
     * interfaces and can be cast to either type to enforce a stricter API.
     *
     * See also {@link Database#listCollections}.
     *
     * @param excludeSystem - Whether system collections should be excluded.
     *
     * @example
     * ```js
     * const db = new Database();
     * const collections = await db.collections();
     * // collections is an array of DocumentCollection and EdgeCollection
     * // instances not including system collections
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * const collections = await db.collections(false);
     * // collections is an array of DocumentCollection and EdgeCollection
     * // instances including system collections
     * ```
     */
    async collections(excludeSystem = true) {
        const collections = await this.listCollections(excludeSystem);
        return collections.map((data) => this.collection(data.name));
    }
    //#endregion
    //#region graphs
    /**
     * Returns a {@link graph.Graph} instance representing the graph with the given
     * `graphName`.
     *
     * @param graphName - Name of the graph.
     *
     * @example
     * ```js
     * const db = new Database();
     * const graph = db.graph("some-graph");
     * ```
     */
    graph(graphName) {
        if (!this._graphs.has(graphName)) {
            this._graphs.set(graphName, new Graph(this, graphName));
        }
        return this._graphs.get(graphName);
    }
    /**
     * Creates a graph with the given `graphName` and `edgeDefinitions`, then
     * returns a {@link graph.Graph} instance for the new graph.
     *
     * @param graphName - Name of the graph to be created.
     * @param edgeDefinitions - An array of edge definitions.
     * @param options - An object defining the properties of the graph.
     */
    async createGraph(graphName, edgeDefinitions, options) {
        const graph = this.graph(graphName);
        await graph.create(edgeDefinitions, options);
        return graph;
    }
    /**
     * Fetches all graphs from the database and returns an array of graph
     * descriptions.
     *
     * See also {@link Database#graphs}.
     *
     * @example
     * ```js
     * const db = new Database();
     * const graphs = await db.listGraphs();
     * // graphs is an array of graph descriptions
     * ```
     */
    listGraphs() {
        return this.request({ path: "/_api/gharial" }, (res) => res.parsedBody.graphs);
    }
    /**
     * Fetches all graphs from the database and returns an array of {@link graph.Graph}
     * instances for those graphs.
     *
     * See also {@link Database#listGraphs}.
     *
     * @example
     * ```js
     * const db = new Database();
     * const graphs = await db.graphs();
     * // graphs is an array of Graph instances
     * ```
     */
    async graphs() {
        const graphs = await this.listGraphs();
        return graphs.map((data) => this.graph(data._key));
    }
    //#endregion
    //#region views
    /**
     * Returns a {@link view.View} instance for the given `viewName`.
     *
     * @param viewName - Name of the ArangoSearch or SearchAlias View.
     *
     * @example
     * ```js
     * const db = new Database();
     * const view = db.view("potatoes");
     * ```
     */
    view(viewName) {
        if (!this._views.has(viewName)) {
            this._views.set(viewName, new View(this, viewName));
        }
        return this._views.get(viewName);
    }
    /**
     * Creates a new View with the given `viewName` and `options`, then returns a
     * {@link view.View} instance for the new View.
     *
     * @param viewName - Name of the View.
     * @param options - An object defining the properties of the View.
     *
     * @example
     * ```js
     * const db = new Database();
     * const view = await db.createView("potatoes", { type: "arangosearch" });
     * // the ArangoSearch View "potatoes" now exists
     * ```
     */
    async createView(viewName, options) {
        const view = this.view(viewName);
        await view.create(options);
        return view;
    }
    /**
     * Renames the view `viewName` to `newName`.
     *
     * Additionally removes any stored {@link view.View} instance for `viewName` from
     * the `Database` instance's internal cache.
     *
     * **Note**: Renaming views may not be supported when ArangoDB is running in
     * a cluster configuration.
     *
     * @param viewName - Current name of the view.
     * @param newName - The new name of the view.
     */
    async renameView(viewName, newName) {
        const result = await this.request({
            method: "PUT",
            path: `/_api/view/${encodeURIComponent(viewName)}/rename`,
            body: { name: newName },
        });
        this._views.delete(viewName);
        return result;
    }
    /**
     * Fetches all Views from the database and returns an array of View
     * descriptions.
     *
     * See also {@link Database#views}.
     *
     * @example
     * ```js
     * const db = new Database();
     *
     * const views = await db.listViews();
     * // views is an array of View descriptions
     * ```
     */
    listViews() {
        return this.request({ path: "/_api/view" }, (res) => res.parsedBody.result);
    }
    /**
     * Fetches all Views from the database and returns an array of
     * {@link view.View} instances
     * for the Views.
     *
     * See also {@link Database#listViews}.
     *
     * @example
     * ```js
     * const db = new Database();
     * const views = await db.views();
     * // views is an array of ArangoSearch View instances
     * ```
     */
    async views() {
        const views = await this.listViews();
        return views.map((data) => this.view(data.name));
    }
    //#endregion
    //#region analyzers
    /**
     * Returns an {@link analyzer.Analyzer} instance representing the Analyzer with the
     * given `analyzerName`.
     *
     * @example
     * ```js
     * const db = new Database();
     * const analyzer = db.analyzer("some-analyzer");
     * const info = await analyzer.get();
     * ```
     */
    analyzer(analyzerName) {
        if (!this._analyzers.has(analyzerName)) {
            this._analyzers.set(analyzerName, new Analyzer(this, analyzerName));
        }
        return this._analyzers.get(analyzerName);
    }
    /**
     * Creates a new Analyzer with the given `analyzerName` and `options`, then
     * returns an {@link analyzer.Analyzer} instance for the new Analyzer.
     *
     * @param analyzerName - Name of the Analyzer.
     * @param options - An object defining the properties of the Analyzer.
     *
     * @example
     * ```js
     * const db = new Database();
     * const analyzer = await db.createAnalyzer("potatoes", { type: "identity" });
     * // the identity Analyzer "potatoes" now exists
     * ```
     */
    async createAnalyzer(analyzerName, options) {
        const analyzer = this.analyzer(analyzerName);
        await analyzer.create(options);
        return analyzer;
    }
    /**
     * Fetches all Analyzers visible in the database and returns an array of
     * Analyzer descriptions.
     *
     * See also {@link Database#analyzers}.
     *
     * @example
     * ```js
     * const db = new Database();
     * const analyzers = await db.listAnalyzers();
     * // analyzers is an array of Analyzer descriptions
     * ```
     */
    listAnalyzers() {
        return this.request({ path: "/_api/analyzer" }, (res) => res.parsedBody.result);
    }
    /**
     * Fetches all Analyzers visible in the database and returns an array of
     * {@link analyzer.Analyzer} instances for those Analyzers.
     *
     * See also {@link Database#listAnalyzers}.
     *
     * @example
     * ```js
     * const db = new Database();
     * const analyzers = await db.analyzers();
     * // analyzers is an array of Analyzer instances
     * ```
     */
    async analyzers() {
        const analyzers = await this.listAnalyzers();
        return analyzers.map((data) => this.analyzer(data.name));
    }
    //#endregion
    /**
     * Fetches all ArangoDB users visible to the authenticated user and returns
     * an array of user objects.
     *
     * @example
     * ```js
     * const db = new Database();
     * const users = await db.listUsers();
     * // users is an array of user objects
     * ```
     */
    listUsers() {
        return this.request({
            path: "/_api/user",
        }, (res) => res.parsedBody.result);
    }
    /**
     * Fetches the user data of a single ArangoDB user.
     *
     * @param username - Name of the ArangoDB user to fetch.
     *
     * @example
     * ```js
     * const db = new Database();
     * const user = await db.getUser("steve");
     * // user is the user object for the user named "steve"
     * ```
     */
    getUser(username) {
        return this.request({
            path: `/_api/user/${encodeURIComponent(username)}`,
        });
    }
    createUser(username, options) {
        if (typeof options === "string") {
            options = { passwd: options };
        }
        return this.request({
            method: "POST",
            path: "/_api/user",
            body: { user: username, ...options },
        }, (res) => res.parsedBody);
    }
    updateUser(username, options) {
        if (typeof options === "string") {
            options = { passwd: options };
        }
        return this.request({
            method: "PATCH",
            path: `/_api/user/${encodeURIComponent(username)}`,
            body: options,
        }, (res) => res.parsedBody);
    }
    /**
     * Replaces the ArangoDB user's option with the new options.
     *
     * @param username - Name of the ArangoDB user to modify.
     * @param options - New options to replace the user's existing options.
     *
     * @example
     * ```js
     * const db = new Database();
     * const user = await db.replaceUser("steve", { passwd: "", active: false });
     * // The user "steve" has been set to inactive with an empty password
     * ```
     */
    replaceUser(username, options) {
        if (typeof options === "string") {
            options = { passwd: options };
        }
        return this.request({
            method: "PUT",
            path: `/_api/user/${encodeURIComponent(username)}`,
            body: options,
        }, (res) => res.parsedBody);
    }
    /**
     * Removes the ArangoDB user with the given username from the server.
     *
     * @param username - Name of the ArangoDB user to remove.
     *
     * @example
     * ```js
     * const db = new Database();
     * await db.removeUser("steve");
     * // The user "steve" has been removed
     * ```
     */
    removeUser(username) {
        return this.request({
            method: "DELETE",
            path: `/_api/user/${encodeURIComponent(username)}`,
        }, (res) => res.parsedBody);
    }
    /**
     * Fetches the given ArangoDB user's access level for the database, or the
     * given collection in the given database.
     *
     * @param username - Name of the ArangoDB user to fetch the access level for.
     * @param database - Database to fetch the access level for.
     * @param collection - Collection to fetch the access level for.
     *
     * @example
     * ```js
     * const db = new Database();
     * const accessLevel = await db.getUserAccessLevel("steve");
     * // The access level of the user "steve" has been fetched for the current
     * // database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * const accessLevel = await db.getUserAccessLevel("steve", {
     *   database: "staging"
     * });
     * // The access level of the user "steve" has been fetched for the "staging"
     * // database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * const accessLevel = await db.getUserAccessLevel("steve", {
     *   collection: "pokemons"
     * });
     * // The access level of the user "steve" has been fetched for the
     * // "pokemons" collection in the current database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * const accessLevel = await db.getUserAccessLevel("steve", {
     *   database: "staging",
     *   collection: "pokemons"
     * });
     * // The access level of the user "steve" has been fetched for the
     * // "pokemons" collection in the "staging" database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * const staging = db.database("staging");
     * const accessLevel = await db.getUserAccessLevel("steve", {
     *   database: staging
     * });
     * // The access level of the user "steve" has been fetched for the "staging"
     * // database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * const staging = db.database("staging");
     * const accessLevel = await db.getUserAccessLevel("steve", {
     *   collection: staging.collection("pokemons")
     * });
     * // The access level of the user "steve" has been fetched for the
     * // "pokemons" collection in database "staging".
     * ```
     */
    getUserAccessLevel(username, { database, collection }) {
        const databaseName = isArangoDatabase(database)
            ? database.name
            : database ??
                (isArangoCollection(collection)
                    ? collection._db.name
                    : this._name);
        const suffix = collection
            ? `/${encodeURIComponent(isArangoCollection(collection) ? collection.name : collection)}`
            : "";
        return this.request({
            path: `/_api/user/${encodeURIComponent(username)}/database/${encodeURIComponent(databaseName)}${suffix}`,
        }, (res) => res.parsedBody.result);
    }
    /**
     * Sets the given ArangoDB user's access level for the database, or the
     * given collection in the given database.
     *
     * @param username - Name of the ArangoDB user to set the access level for.
     * @param database - Database to set the access level for.
     * @param collection - Collection to set the access level for.
     * @param grant - Access level to set for the given user.
     *
     * @example
     * ```js
     * const db = new Database();
     * await db.setUserAccessLevel("steve", { grant: "rw" });
     * // The user "steve" now has read-write access to the current database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * await db.setUserAccessLevel("steve", {
     *   database: "staging",
     *   grant: "rw"
     * });
     * // The user "steve" now has read-write access to the "staging" database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * await db.setUserAccessLevel("steve", {
     *   collection: "pokemons",
     *   grant: "rw"
     * });
     * // The user "steve" now has read-write access to the "pokemons" collection
     * // in the current database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * await db.setUserAccessLevel("steve", {
     *   database: "staging",
     *   collection: "pokemons",
     *   grant: "rw"
     * });
     * // The user "steve" now has read-write access to the "pokemons" collection
     * // in the "staging" database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * const staging = db.database("staging");
     * await db.setUserAccessLevel("steve", {
     *   database: staging,
     *   grant: "rw"
     * });
     * // The user "steve" now has read-write access to the "staging" database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * const staging = db.database("staging");
     * await db.setUserAccessLevel("steve", {
     *   collection: staging.collection("pokemons"),
     *   grant: "rw"
     * });
     * // The user "steve" now has read-write access to the "pokemons" collection
     * // in database "staging".
     * ```
     */
    setUserAccessLevel(username, { database, collection, grant, }) {
        const databaseName = isArangoDatabase(database)
            ? database.name
            : database ??
                (isArangoCollection(collection)
                    ? collection._db.name
                    : this._name);
        const suffix = collection
            ? `/${encodeURIComponent(isArangoCollection(collection) ? collection.name : collection)}`
            : "";
        return this.request({
            method: "PUT",
            path: `/_api/user/${encodeURIComponent(username)}/database/${encodeURIComponent(databaseName)}${suffix}`,
            body: { grant },
        }, (res) => res.parsedBody);
    }
    /**
     * Clears the given ArangoDB user's access level for the database, or the
     * given collection in the given database.
     *
     * @param username - Name of the ArangoDB user to clear the access level for.
     * @param database - Database to clear the access level for.
     * @param collection - Collection to clear the access level for.
     *
     * @example
     * ```js
     * const db = new Database();
     * await db.clearUserAccessLevel("steve");
     * // The access level of the user "steve" has been cleared for the current
     * // database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * await db.clearUserAccessLevel("steve", { database: "staging" });
     * // The access level of the user "steve" has been cleared for the "staging"
     * // database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * await db.clearUserAccessLevel("steve", { collection: "pokemons" });
     * // The access level of the user "steve" has been cleared for the
     * // "pokemons" collection in the current database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * await db.clearUserAccessLevel("steve", {
     *   database: "staging",
     *   collection: "pokemons"
     * });
     * // The access level of the user "steve" has been cleared for the
     * // "pokemons" collection in the "staging" database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * const staging = db.database("staging");
     * await db.clearUserAccessLevel("steve", { database: staging });
     * // The access level of the user "steve" has been cleared for the "staging"
     * // database.
     * ```
     *
     * @example
     * ```js
     * const db = new Database();
     * const staging = db.database("staging");
     * await db.clearUserAccessLevel("steve", {
     *   collection: staging.collection("pokemons")
     * });
     * // The access level of the user "steve" has been cleared for the
     * // "pokemons" collection in database "staging".
     * ```
     */
    clearUserAccessLevel(username, { database, collection }) {
        const databaseName = isArangoDatabase(database)
            ? database.name
            : database ??
                (isArangoCollection(collection)
                    ? collection._db.name
                    : this._name);
        const suffix = collection
            ? `/${encodeURIComponent(isArangoCollection(collection) ? collection.name : collection)}`
            : "";
        return this.request({
            method: "DELETE",
            path: `/_api/user/${encodeURIComponent(username)}/database/${encodeURIComponent(databaseName)}${suffix}`,
        }, (res) => res.parsedBody);
    }
    getUserDatabases(username, full) {
        return this.request({
            path: `/_api/user/${encodeURIComponent(username)}/database`,
            search: { full },
        }, (res) => res.parsedBody.result);
    }
    executeTransaction(collections, action, options = {}) {
        const { allowDirtyRead = undefined, ...opts } = options;
        return this.request({
            method: "POST",
            path: "/_api/transaction",
            allowDirtyRead,
            body: {
                collections: coerceTransactionCollections(collections),
                action,
                ...opts,
            },
        }, (res) => res.parsedBody.result);
    }
    /**
     * Returns a {@link transaction.Transaction} instance for an existing streaming
     * transaction with the given `id`.
     *
     * See also {@link Database#beginTransaction}.
     *
     * @param id - The `id` of an existing stream transaction.
     *
     * @example
     * ```js
     * const trx1 = await db.beginTransaction(collections);
     * const id = trx1.id;
     * // later
     * const trx2 = db.transaction(id);
     * await trx2.commit();
     * ```
     */
    transaction(transactionId) {
        return new Transaction(this, transactionId);
    }
    beginTransaction(collections, options = {}) {
        const { allowDirtyRead = undefined, ...opts } = options;
        return this.request({
            method: "POST",
            path: "/_api/transaction/begin",
            allowDirtyRead,
            body: {
                collections: coerceTransactionCollections(collections),
                ...opts,
            },
        }, (res) => new Transaction(this, res.parsedBody.result.id));
    }
    async withTransaction(collections, callback, options = {}) {
        const trx = await this.beginTransaction(collections, options);
        try {
            const result = await callback((fn) => trx.step(fn));
            await trx.commit();
            return result;
        }
        catch (e) {
            try {
                await trx.abort();
            }
            catch { }
            throw e;
        }
    }
    /**
     * Fetches all active transactions from the database and returns an array of
     * transaction descriptions.
     *
     * See also {@link Database#transactions}.
     *
     * @example
     * ```js
     * const db = new Database();
     * const transactions = await db.listTransactions();
     * // transactions is an array of transaction descriptions
     * ```
     */
    listTransactions() {
        return this._connection.request({ path: "/_api/transaction" }, (res) => res.parsedBody.transactions);
    }
    /**
     * Fetches all active transactions from the database and returns an array of
     * {@link transaction.Transaction} instances for those transactions.
     *
     * See also {@link Database#listTransactions}.
     *
     * @example
     * ```js
     * const db = new Database();
     * const transactions = await db.transactions();
     * // transactions is an array of transactions
     * ```
     */
    async transactions() {
        const transactions = await this.listTransactions();
        return transactions.map((data) => this.transaction(data.id));
    }
    query(query, bindVars, options = {}) {
        if (isAqlQuery(query)) {
            options = bindVars ?? {};
            bindVars = query.bindVars;
            query = query.query;
        }
        else if (isAqlLiteral(query)) {
            query = query.toAQL();
        }
        const { allowDirtyRead, retryOnConflict, count, batchSize, cache, memoryLimit, ttl, timeout, ...opts } = options;
        return this.request({
            method: "POST",
            path: "/_api/cursor",
            body: {
                query,