diff --git a/.gitignore b/.gitignore index 44669b6..35a1a40 100644 --- a/.gitignore +++ b/.gitignore @@ -34,7 +34,7 @@ Thumbs.db # Dgraph - +dgraph-local-data/ tls/ p/ w/ diff --git a/README.md b/README.md index 49ddd6d..e0b83c7 100644 --- a/README.md +++ b/README.md @@ -51,23 +51,40 @@ of using the Dgraph JavaScript client. Follow the instructions in the README of ### Creating a Client -A `DgraphClient` object can be initialised by passing it a list of `DgraphClientStub` clients as -variadic arguments. Connecting to multiple Dgraph servers in the same cluster allows for better -distribution of workload. +#### Connection Strings -The following code snippet shows just one connection. +The dgraph-js supports connecting to a Dgraph cluster using connection strings. Dgraph connections +strings take the form `dgraph://{username:password@}host:port?args`. + +`username` and `password` are optional. If username is provided, a password must also be present. If +supplied, these credentials are used to log into a Dgraph cluster through the ACL mechanism. + +Valid connection string args: + +| Arg | Value | Description | +| ----------- | ------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| apikey | \ | a Dgraph Cloud API Key | +| bearertoken | \ | an access token | +| sslmode | disable \| require \| verify-ca | TLS option, the default is `disable`. If `verify-ca` is set, the TLS certificate configured in the Dgraph cluster must be from a valid certificate authority. | + +## Some example connection strings + +| Value | Explanation | +| ------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------- | +| dgraph://localhost:9080 | Connect to localhost, no ACL, no TLS | +| dgraph://sally:supersecret@dg.example.com:443?sslmode=verify-ca | Connect to remote server, use ACL and require TLS and a valid certificate from a CA | +| dgraph://foo-bar.grpc.us-west-2.aws.cloud.dgraph.io:443?sslmode=verify-ca&apikey=\ | Connect to a Dgraph Cloud cluster | +| dgraph://foo-bar.grpc.hypermode.com?sslmode=verify-ca&bearertoken=\ | Connect to a Dgraph cluster protected by a secure gateway | + +Using the `open` function with a connection string: ```js -const dgraph = require("dgraph-js") -const grpc = require("@grpc/grpc-js") - -const clientStub = new dgraph.DgraphClientStub( - // addr: optional, default: "localhost:9080" - "localhost:9080", - // credentials: optional, default: grpc.credentials.createInsecure() - grpc.credentials.createInsecure(), -) -const dgraphClient = new dgraph.DgraphClient(clientStub) +// open a connection to an ACL-enabled, non-TLS cluster and login as groot +const client = await dgraph.open("dgraph://groot:password@localhost:8090") +// Use the client + +// this will close all the client stubs +client.close() ``` To facilitate debugging, [debug mode](#debug-mode) can be enabled for a client. @@ -89,25 +106,6 @@ In the example above, the client logs into namespace `123` using username `groot `password`. Once logged in, the client can perform all the operations allowed to the `groot` user of namespace `123`. -### Creating a Client for Dgraph Cloud Endpoint - -If you want to connect to Dgraph running on your [Dgraph Cloud](https://cloud.dgraph.io) instance, -then all you need is the URL of your Dgraph Cloud endpoint and the API key. You can get a client -using them as follows: - -```js -const dgraph = require("dgraph-js") - -const clientStub = dgraph.clientStubFromCloudEndpoint( - "https://frozen-mango.eu-central-1.aws.cloud.dgraph.io/graphql", - "", -) -const dgraphClient = new dgraph.DgraphClient(clientStub) -``` - -**Note:** the `clientStubFromSlashGraphQLEndpoint` method is deprecated and will be removed in the -next release. Instead use `clientStubFromCloudEndpoint` method. - ### Altering the Database To set the schema, create an `Operation` object, set the schema and pass it to @@ -376,27 +374,21 @@ try { ### Cleanup Resources -To cleanup resources, you have to call `DgraphClientStub#close()` individually for all the instances -of `DgraphClientStub`. +To cleanup resources, you have to call `close()`. ```js const SERVER_ADDR = "localhost:9080" const SERVER_CREDENTIALS = grpc.credentials.createInsecure() -// Create instances of DgraphClientStub. -const stub1 = new dgraph.DgraphClientStub(SERVER_ADDR, SERVER_CREDENTIALS) -const stub2 = new dgraph.DgraphClientStub(SERVER_ADDR, SERVER_CREDENTIALS) - -// Create an instance of DgraphClient. -const dgraphClient = new dgraph.DgraphClient(stub1, stub2) +// Create instances of DgraphClient. +const client = await dgraph.open("dgraph://groot:password@${SERVER_ADDR}") // ... // Use dgraphClient // ... -// Cleanup resources by closing all client stubs. -stub1.close() -stub2.close() +// Cleanup resources by closing client stubs. +client.close() ``` ### Debug mode diff --git a/examples/simple/index.js b/examples/simple/index.js index ba135f3..320e2d1 100644 --- a/examples/simple/index.js +++ b/examples/simple/index.js @@ -1,14 +1,4 @@ -const dgraph = require("dgraph-js") - -// Create a client stub. -function newClientStub() { - return new dgraph.DgraphClientStub("localhost:9080") -} - -// Create a client. -function newClient(clientStub) { - return new dgraph.DgraphClient(clientStub) -} +import * as dgraph from "dgraph-js" // Drop All - discard all data, schema and start from a clean slate. async function dropAll(dgraphClient) { @@ -125,8 +115,7 @@ async function queryData(dgraphClient) { } async function main() { - const dgraphClientStub = newClientStub() - const dgraphClient = newClient(dgraphClientStub) + const dgraphClient = await dgraph.open("dgraph://groot:password@localhost:9080") await dropAll(dgraphClient) await setSchema(dgraphClient) await createData(dgraphClient) @@ -137,7 +126,7 @@ async function main() { await queryData(dgraphClient) // Close the client stub. - dgraphClientStub.close() + dgraphClient.close() } main() diff --git a/examples/simple/package.json b/examples/simple/package.json index 473ed52..84162f2 100644 --- a/examples/simple/package.json +++ b/examples/simple/package.json @@ -1,5 +1,6 @@ { "name": "simple", + "type": "module", "dependencies": { "dgraph-js": "^24.1.0", "@grpc/grpc-js": "1.8.22" diff --git a/examples/tls/index.js b/examples/tls/index.js index 17b33e1..28619ae 100644 --- a/examples/tls/index.js +++ b/examples/tls/index.js @@ -1,43 +1,43 @@ const fs = require('fs'); const path = require('path'); -const dgraph = require("dgraph-js"); +import * as dgraph from "dgraph-js"; // Create a client stub. function newClientStub() { - // First create the appropriate TLS certs with dgraph cert: - // $ dgraph cert - // $ dgraph cert -n localhost - // $ dgraph cert -c user - console.log(path.join(__dirname, "tls", "ca.crt")); - const rootCaCert = fs.readFileSync(path.join(__dirname, "tls", "ca.crt")); - const clientCertKey = fs.readFileSync( - path.join(__dirname, "tls", "client.user.key") - ); - const clientCert = fs.readFileSync( - path.join(__dirname, "tls", "client.user.crt") - ); - return new dgraph.DgraphClientStub( - "localhost:9080", - dgraph.grpc.credentials.createSsl(rootCaCert, clientCertKey, clientCert) - ); + // First create the appropriate TLS certs with dgraph cert: + // $ dgraph cert + // $ dgraph cert -n localhost + // $ dgraph cert -c user + console.log(path.join(__dirname, "tls", "ca.crt")); + const rootCaCert = fs.readFileSync(path.join(__dirname, "tls", "ca.crt")); + const clientCertKey = fs.readFileSync( + path.join(__dirname, "tls", "client.user.key") + ); + const clientCert = fs.readFileSync( + path.join(__dirname, "tls", "client.user.crt") + ); + return new dgraph.DgraphClientStub( + "localhost:9080", + dgraph.grpc.credentials.createSsl(rootCaCert, clientCertKey, clientCert) + ); } // Create a client. function newClient(clientStub) { - return new dgraph.DgraphClient(clientStub); + return new dgraph.DgraphClient(clientStub); } // Drop All - discard all data and start from a clean slate. async function dropAll(dgraphClient) { - const op = new dgraph.Operation(); - op.setDropAll(true); - await dgraphClient.alter(op); + const op = new dgraph.Operation(); + op.setDropAll(true); + await dgraphClient.alter(op); } // Set schema. async function setSchema(dgraphClient) { - const schema = ` + const schema = ` name: string @index(exact) . age: int . married: bool . @@ -45,77 +45,77 @@ async function setSchema(dgraphClient) { dob: datetime . friend: [uid] @reverse . `; - const op = new dgraph.Operation(); - op.setSchema(schema); - await dgraphClient.alter(op); + const op = new dgraph.Operation(); + op.setSchema(schema); + await dgraphClient.alter(op); } // Create data using JSON. async function createData(dgraphClient) { - // Create a new transaction. - const txn = dgraphClient.newTxn(); - try { - // Create data. - const p = { - uid: "_:alice", - name: "Alice", - age: 26, - married: true, - loc: { - type: "Point", - coordinates: [1.1, 2], - }, - dob: new Date(1980, 1, 1, 23, 0, 0, 0), - friend: [ - { - name: "Bob", - age: 24, - }, - { - name: "Charlie", - age: 29, - }, - ], - school: [ - { - name: "Crown Public School", - }, - ], - }; + // Create a new transaction. + const txn = dgraphClient.newTxn(); + try { + // Create data. + const p = { + uid: "_:alice", + name: "Alice", + age: 26, + married: true, + loc: { + type: "Point", + coordinates: [1.1, 2], + }, + dob: new Date(1980, 1, 1, 23, 0, 0, 0), + friend: [ + { + name: "Bob", + age: 24, + }, + { + name: "Charlie", + age: 29, + }, + ], + school: [ + { + name: "Crown Public School", + }, + ], + }; - // Run mutation. - const mu = new dgraph.Mutation(); - mu.setSetJson(p); - const response = await txn.mutate(mu); + // Run mutation. + const mu = new dgraph.Mutation(); + mu.setSetJson(p); + const response = await txn.mutate(mu); - // Commit transaction. - await txn.commit(); + // Commit transaction. + await txn.commit(); - // Get uid of the outermost object (person named "Alice"). - // Response#getUidsMap() returns a map from blank node names to uids. - // For a json mutation, blank node label is used for the name of the created nodes. - console.log( - `Created person named "Alice" with uid = ${response - .getUidsMap() - .get("alice")}\n` - ); + // Get uid of the outermost object (person named "Alice"). + // Response#getUidsMap() returns a map from blank node names to uids. + // For a json mutation, blank node label is used for the name of the created nodes. + console.log( + `Created person named "Alice" with uid = ${response + .getUidsMap() + .get("alice")}\n` + ); - console.log("All created nodes (map from blank node names to uids):"); - response - .getUidsMap() - .forEach((uid, key) => console.log(`${key} => ${uid}`)); - console.log(); - } finally { - // Clean up. Calling this after txn.commit() is a no-op - // and hence safe. - await txn.discard(); - } + console.log("All created nodes (map from blank node names to uids):"); + response + .getUidsMap() + .forEach((uid, key) => console.log(`${key} => ${uid}`)); + console.log(); + } finally { + // Clean up. Calling this after txn.commit() is a no-op + // and hence safe. + await txn.discard(); + } } // Query for data. async function queryData(dgraphClient) { - // Run query. - const query = `query all($a: string) { + // Run query. + const query = `query all($a: string) { all(func: eq(name, $a)) { uid name @@ -132,33 +132,33 @@ async function queryData(dgraphClient) { } } }`; - const vars = { $a: "Alice" }; - const res = await dgraphClient - .newTxn({ readOnly: true }) - .queryWithVars(query, vars); - const ppl = res.getJson(); + const vars = { $a: "Alice" }; + const res = await dgraphClient + .newTxn({ readOnly: true }) + .queryWithVars(query, vars); + const ppl = res.getJson(); - // Print results. - console.log(`Number of people named "Alice": ${ppl.all.length}`); - ppl.all.forEach((person) => console.log(person)); + // Print results. + console.log(`Number of people named "Alice": ${ppl.all.length}`); + ppl.all.forEach((person) => console.log(person)); } async function main() { - const dgraphClientStub = newClientStub(); - const dgraphClient = newClient(dgraphClientStub); - await dropAll(dgraphClient); - await setSchema(dgraphClient); - await createData(dgraphClient); - await queryData(dgraphClient); + const dgraphClientStub = newClientStub(); + const dgraphClient = newClient(dgraphClientStub); + await dropAll(dgraphClient); + await setSchema(dgraphClient); + await createData(dgraphClient); + await queryData(dgraphClient); - // Close the client stub. - dgraphClientStub.close(); + // Close the client stub. + dgraphClientStub.close(); } main() - .then(() => { - console.log("\nDONE!"); - }) - .catch((e) => { - console.log("ERROR: ", e); - }); + .then(() => { + console.log("\nDONE!"); + }) + .catch((e) => { + console.log("ERROR: ", e); + }); diff --git a/examples/tls/package.json b/examples/tls/package.json index b9ef9e4..9b9091c 100644 --- a/examples/tls/package.json +++ b/examples/tls/package.json @@ -1,10 +1,11 @@ { "name": "tls", + "type": "module", "dependencies": { "dgraph-js": "^24.0.0" }, "scripts": { "example": "node index.js", - "clean": "rm -rf node_modules" + "clean": "rm -fr node_modules" } } diff --git a/lib/client.d.ts b/lib/client.d.ts index c2df751..f0b561a 100644 --- a/lib/client.d.ts +++ b/lib/client.d.ts @@ -21,6 +21,8 @@ export declare class DgraphClient { setDebugMode(mode?: boolean): void debug(msg: string): void anyClient(): DgraphClientStub + close(): void } export declare function isJwtExpired(err: any): boolean export declare function deleteEdges(mu: types.Mutation, uid: string, ...predicates: string[]): void +export declare function open(connStr: string): Promise diff --git a/lib/client.js b/lib/client.js index afb01cd..a0ee891 100644 --- a/lib/client.js +++ b/lib/client.js @@ -141,11 +141,18 @@ Object.defineProperty(exports, "__esModule", { value: true }) exports.DgraphClient = void 0 exports.isJwtExpired = isJwtExpired exports.deleteEdges = deleteEdges +exports.open = open +var grpc = require("@grpc/grpc-js") var messages = require("../generated/api_pb") +var clientStub_1 = require("./clientStub") var errors_1 = require("./errors") var txn_1 = require("./txn") var types = require("./types") var util_1 = require("./util") +var dgraphScheme = "dgraph:" +var sslModeDisable = "disable" +var sslModeRequire = "require" +var sslModeVerifyCA = "verify-ca" var DgraphClient = (function () { function DgraphClient() { var clients = [] @@ -220,6 +227,16 @@ var DgraphClient = (function () { DgraphClient.prototype.anyClient = function () { return this.clients[Math.floor(Math.random() * this.clients.length)] } + DgraphClient.prototype.close = function () { + this.clients.forEach(function (clientStub) { + try { + clientStub.close() + console.log("Closed client stub successfully") + } catch (error) { + console.error("Failed to close client stub:", error) + } + }) + } return DgraphClient })() exports.DgraphClient = DgraphClient @@ -245,3 +262,99 @@ function deleteEdges(mu, uid) { mu.addDel(nquad) } } +function addApiKeyToCredentials(baseCreds, apiKey) { + var metaCreds = grpc.credentials.createFromMetadataGenerator(function (_, callback) { + var metadata = new grpc.Metadata() + metadata.add("authorization", apiKey) + callback(null, metadata) + }) + return grpc.credentials.combineChannelCredentials(baseCreds, metaCreds) +} +function addBearerTokenToCredentials(baseCreds, bearerToken) { + var metaCreds = grpc.credentials.createFromMetadataGenerator(function (_, callback) { + var metadata = new grpc.Metadata() + metadata.add("Authorization", "Bearer ".concat(bearerToken)) + callback(null, metadata) + }) + return grpc.credentials.combineChannelCredentials(baseCreds, metaCreds) +} +function open(connStr) { + return __awaiter(this, void 0, void 0, function () { + var parsedUrl, host, port, queryParams, sslMode, credentials, clientStub, err_1 + return __generator(this, function (_a) { + switch (_a.label) { + case 0: + parsedUrl = new URL(connStr) + if (parsedUrl.protocol !== dgraphScheme) { + throw new Error("Invalid scheme: must start with dgraph://") + } + host = parsedUrl.hostname + port = parsedUrl.port + if (!host) { + throw new Error("Invalid connection string: hostname required") + } + if (!port) { + throw new Error("Invalid connection string: port required") + } + queryParams = {} + if (parsedUrl.searchParams) { + parsedUrl.searchParams.forEach(function (value, key) { + queryParams[key] = value + }) + } + if (queryParams.apikey && queryParams.bearertoken) { + throw new Error("Both apikey and bearertoken cannot be provided") + } + sslMode = queryParams.sslmode + if (sslMode === undefined) { + sslMode = sslModeDisable + } + switch (sslMode) { + case sslModeDisable: + credentials = grpc.credentials.createInsecure() + break + case sslModeRequire: + credentials = grpc.credentials.createSsl(null, null, null, { + checkServerIdentity: function () { + return undefined + }, + }) + break + case sslModeVerifyCA: + credentials = grpc.credentials.createSsl() + break + default: + throw new Error( + "Invalid SSL mode: ".concat( + sslMode, + " (must be one of disable, require, verify-ca)", + ), + ) + } + if (queryParams.apikey) { + credentials = addApiKeyToCredentials(credentials, queryParams.apikey) + } else if (queryParams.bearertoken) { + credentials = addBearerTokenToCredentials(credentials, queryParams.bearertoken) + } + clientStub = new clientStub_1.DgraphClientStub( + "".concat(host, ":").concat(port), + credentials, + ) + if (!(parsedUrl.username != "")) return [3, 4] + if (!(parsedUrl.password === "")) return [3, 1] + throw new Error("Invalid connection string: password required when username is provided") + case 1: + _a.trys.push([1, 3, , 4]) + return [4, clientStub.login(parsedUrl.username, parsedUrl.password)] + case 2: + _a.sent() + return [3, 4] + case 3: + err_1 = _a.sent() + throw new Error("Failed to sign in user: ".concat(err_1.message)) + case 4: + return [2, new DgraphClient(clientStub)] + } + }) + }) +} diff --git a/lib/types.d.ts b/lib/types.d.ts index 5e26724..94da375 100644 --- a/lib/types.d.ts +++ b/lib/types.d.ts @@ -5,25 +5,25 @@ import * as messages from "../generated/api_pb" export declare class Payload extends messages.Payload { - getData(): string | Uint8Array + getData(): any getData_asB64(): string getData_asU8(): Uint8Array setData(value: any): void } export declare function createPayload(oldPayload: messages.Payload): Payload export declare class Response extends messages.Response { - getJson(): string | Uint8Array + getJson(): any getJson_asB64(): string getJson_asU8(): Uint8Array setJson(value: any): void } export declare function createResponse(oldResponse: messages.Response): Response export declare class Mutation extends messages.Mutation { - getSetJson(): string | Uint8Array + getSetJson(): any getSetJson_asB64(): string getSetJson_asU8(): Uint8Array setSetJson(value: any): void - getDeleteJson(): string | Uint8Array + getDeleteJson(): any getDeleteJson_asB64(): string getDeleteJson_asU8(): Uint8Array setDeleteJson(value: any): void diff --git a/package-lock.json b/package-lock.json index ecef68b..3d9c7db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3,6 +3,7 @@ "version": "24.1.0", "lockfileVersion": 3, "requires": true, + "type":"module", "packages": { "": { "name": "dgraph-js", diff --git a/package.json b/package.json index f8fbf59..2f900e7 100644 --- a/package.json +++ b/package.json @@ -1,9 +1,9 @@ { "name": "dgraph-js", "version": "24.1.0", - "type": "module", "description": "Official javascript client for Dgraph", "license": "Apache-2.0", + "type": "module", "repository": { "type": "git", "url": "https://github.com/hypermodeinc/dgraph-js.git" diff --git a/src/client.ts b/src/client.ts index e1889b1..16f1af5 100644 --- a/src/client.ts +++ b/src/client.ts @@ -13,6 +13,11 @@ import { Txn, TxnOptions } from "./txn" import * as types from "./types" import { isUnauthenticatedError, stringifyMessage } from "./util" +const dgraphScheme = "dgraph:" +const sslModeDisable = "disable" +const sslModeRequire = "require" +const sslModeVerifyCA = "verify-ca" + /** * Client is a transaction aware client to a set of Dgraph server instances. */ @@ -97,6 +102,17 @@ export class DgraphClient { public anyClient(): DgraphClientStub { return this.clients[Math.floor(Math.random() * this.clients.length)] } + + public close(): void { + this.clients.forEach((clientStub) => { + try { + clientStub.close() // Call the close method on each client stub + console.log("Closed client stub successfully") + } catch (error) { + console.error("Failed to close client stub:", error) + } + }) + } } // isJwtExpired returns true if the error indicates that the jwt has expired. @@ -127,3 +143,100 @@ export function deleteEdges(mu: types.Mutation, uid: string, ...predicates: stri mu.addDel(nquad) } } + +function addApiKeyToCredentials( + baseCreds: grpc.ChannelCredentials, + apiKey: string, +): grpc.ChannelCredentials { + const metaCreds = grpc.credentials.createFromMetadataGenerator((_, callback) => { + const metadata = new grpc.Metadata() + metadata.add("authorization", apiKey) + callback(null, metadata) + }) + return grpc.credentials.combineChannelCredentials(baseCreds, metaCreds) +} + +function addBearerTokenToCredentials( + baseCreds: grpc.ChannelCredentials, + bearerToken: string, +): grpc.ChannelCredentials { + const metaCreds = grpc.credentials.createFromMetadataGenerator((_, callback) => { + const metadata = new grpc.Metadata() + metadata.add("Authorization", `Bearer ${bearerToken}`) + callback(null, metadata) + }) + return grpc.credentials.combineChannelCredentials(baseCreds, metaCreds) +} + +export async function open(connStr: string): Promise { + const parsedUrl = new URL(connStr) + if (parsedUrl.protocol !== dgraphScheme) { + throw new Error("Invalid scheme: must start with dgraph://") + } + + const host = parsedUrl.hostname + const port = parsedUrl.port + if (!host) { + throw new Error("Invalid connection string: hostname required") + } + if (!port) { + throw new Error("Invalid connection string: port required") + } + + // Parse query parameters using searchParams + const queryParams: Record = {} + if (parsedUrl.searchParams) { + parsedUrl.searchParams.forEach((value, key) => { + queryParams[key] = value + }) + } + + if (queryParams.apikey && queryParams.bearertoken) { + throw new Error("Both apikey and bearertoken cannot be provided") + } + + let sslMode = queryParams.sslmode + if (sslMode === undefined) { + sslMode = sslModeDisable + } + + let credentials + switch (sslMode) { + case sslModeDisable: + credentials = grpc.credentials.createInsecure() + break + case sslModeRequire: + credentials = grpc.credentials.createSsl(null, null, null, { + checkServerIdentity: () => undefined, // Skip certificate verification + }) + break + case sslModeVerifyCA: + credentials = grpc.credentials.createSsl() // Use system CA for verification + break + default: + throw new Error(`Invalid SSL mode: ${sslMode} (must be one of disable, require, verify-ca)`) + } + + // Add API key or Bearer token to credentials if provided + if (queryParams.apikey) { + credentials = addApiKeyToCredentials(credentials, queryParams.apikey) + } else if (queryParams.bearertoken) { + credentials = addBearerTokenToCredentials(credentials, queryParams.bearertoken) + } + + const clientStub = new DgraphClientStub(`${host}:${port}`, credentials) + + if (parsedUrl.username != "") { + if (parsedUrl.password === "") { + throw new Error("Invalid connection string: password required when username is provided") + } else { + try { + await clientStub.login(parsedUrl.username, parsedUrl.password) + } catch (err) { + throw new Error(`Failed to sign in user: ${err.message}`) + } + } + } + + return new DgraphClient(clientStub) +} diff --git a/src/clientStubFromSlash.ts b/src/clientStubFromSlash.ts index d33d4b7..a65e57f 100644 --- a/src/clientStubFromSlash.ts +++ b/src/clientStubFromSlash.ts @@ -17,6 +17,10 @@ export function clientStubFromSlashGraphQLEndpoint(graphqlEndpoint: string, apiK return clientStubFromCloudEndpoint(graphqlEndpoint, apiKey) } +/** + * @deprecated + * Please use {@link Open} instead. + */ export function clientStubFromCloudEndpoint(graphqlEndpoint: string, apiKey: string) { const url = new Url(graphqlEndpoint) const urlParts = url.host.split(".") diff --git a/tests/integration/connect.spec.ts b/tests/integration/connect.spec.ts new file mode 100644 index 0000000..3ef3e16 --- /dev/null +++ b/tests/integration/connect.spec.ts @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: © Hypermode Inc. + * SPDX-License-Identifier: Apache-2.0 + */ + +import * as dgraph from "../../src" + +import { SERVER_ADDR } from "../helper" + +describe("open function", () => { + it("should connect with authentication and execute a query", async () => { + const url = `dgraph://groot:password@${SERVER_ADDR}` + const client = await dgraph.open(url) + const query = ` + { + me(func: uid(1)) { + uid + } + } + ` + const txn = client.newTxn({ readOnly: true }) + const response = await txn.query(query) + + // Assertions + expect(response).not.toBeNull() + const parsedJson = response.getJson() // No need for JSON.parse + expect(parsedJson.me[0].uid).toBe("0x1") + client.close() + }) + + it("should throw an error for invalid scheme", async () => { + const invalidUrl = `http://${SERVER_ADDR}` + await expect(async () => dgraph.open(invalidUrl)).rejects.toThrowError( + "Invalid scheme: must start with dgraph://", + ) + }) + + it("should throw an error for missing hostname", async () => { + const invalidUrl = `dgraph://:9081` + await expect(async () => dgraph.open(invalidUrl)).rejects.toThrowError("Invalid URL") + }) + + it("should throw an error for missing port", async () => { + const invalidUrl = `dgraph://localhost` + await expect(async () => await dgraph.open(invalidUrl)).rejects.toThrowError( + "Invalid connection string: port required", + ) + }) + + it("should throw an error for username without password", async () => { + const invalidUrl = `dgraph://groot@${SERVER_ADDR}` + await expect(async () => await dgraph.open(invalidUrl)).rejects.toThrowError( + "Invalid connection string: password required when username is provided", + ) + }) + + it("should throw an error for unsupported sslmode", async () => { + const invalidUrl = `dgraph://${SERVER_ADDR}?sslmode=invalidsllmode` + await expect(async () => await dgraph.open(invalidUrl)).rejects.toThrowError( + "Invalid SSL mode: invalidsllmode (must be one of disable, require, verify-ca)", + ) + }) + + it("should fail login with invalid credentials", async () => { + const invalidUrl = `dgraph://groot:wrongpassword@${SERVER_ADDR}` + await expect(async () => await dgraph.open(invalidUrl)).rejects.toThrowError( + "Failed to sign in user:", + ) + }) +})