https://github.com/stargate/stargate-grpc-node-client
A JS gRPC client for https://stargate.io
https://github.com/stargate/stargate-grpc-node-client
grpc javascript nodejs stargate typescript
Last synced: 11 months ago
JSON representation
A JS gRPC client for https://stargate.io
- Host: GitHub
- URL: https://github.com/stargate/stargate-grpc-node-client
- Owner: stargate
- License: apache-2.0
- Created: 2021-09-24T18:39:41.000Z (over 4 years ago)
- Default Branch: main
- Last Pushed: 2022-01-21T14:15:37.000Z (about 4 years ago)
- Last Synced: 2024-08-09T17:13:28.147Z (over 1 year ago)
- Topics: grpc, javascript, nodejs, stargate, typescript
- Language: JavaScript
- Homepage:
- Size: 182 KB
- Stars: 4
- Watchers: 3
- Forks: 1
- Open Issues: 2
-
Metadata Files:
- Readme: README.md
- Contributing: CONTRIBUTING.md
- License: LICENSE
- Code of conduct: CODE_OF_CONDUCT.md
Awesome Lists containing this project
README
# Stargate Node gRPC Client
This package provides the ability for Node applications to communicate with the [Stargate data gateway](https://stargate.io/) via gRPC.
**Note: This package is for use in applications running on Node servers, NOT gRPC web clients running in browsers.**
- [Quick start guide](#quick-start-guide)
* [Connecting](#connecting)
* [Authentication](#authentication)
+ [Generating authentication metadata](#generating-authentication-metadata)
* [Querying](#querying)
* [Processing the result set](#processing-the-result-set)
+ [Reading primitive values](#reading-primitive-values)
+ [Reading CQL data types](#reading-cql-data-types)
* [Promise support](#promise-support)
* [Example uses](#example-uses)
- [Issue Management](#issue-management)
## Quick start guide
To begin, you'll need to add the necessary dependency to your project:
Using NPM:
```shell
npm i @stargate-oss/stargate-grpc-node-client
```
Using Yarn:
```shell
yarn add @stargate-oss/stargate-grpc-node-client
```
If you don't already have access to a Stargate deployment one can be started quickly for testing using the following Docker
command to run Stargate locally in developer mode and expose port 8090 for gRPC connections:
```shell
docker run --name stargate \
-p 8081:8081 \
-p 8090:8090 \
-d \
-e CLUSTER_NAME=stargate \
-e CLUSTER_VERSION=3.11 \
-e DEVELOPER_MODE=true \
stargateio/stargate-3_11:v1.0.40
```
Ensure the local instance of Stargate is running properly by tailing the logs for the container with `docker logs -f stargate`. When you see this message, Stargate is ready for traffic:
`Finished starting bundles.`
### Connecting
To connect to your Stargate instance set up the client as follows. This example assumes that you're running Stargate locally
with the default credentials of `cassandra/cassandra`. For more information regarding authentication please see the
[Stargate authentication and authorization docs](https://stargate.io/docs/stargate/1.0/developers-guide/authnz.html).
```typescript
import * as grpc from "@grpc/grpc-js";
import { StargateClient, StargateTableBasedToken, Query, Response, promisifyStargateClient } from "@stargate-oss/stargate-grpc-node-client";
// Create a client for Stargate/Cassandra authentication using the default C* username and password
const creds = new StargateTableBasedToken({authEndpoint: 'http://localhost:8081/v1/auth', username: 'cassandra', password: 'cassandra'});
// Create the gRPC client, passing it the address of the gRPC endpoint
const stargateClient = new StargateClient('localhost:8090', grpc.credentials.createInsecure());
// Create a promisified version of the client, so we don't need to use callbacks
const promisifiedClient = promisifyStargateClient(stargateClient);
```
### Authentication
This client supports both table-based and JWT-based authentication to Stargate.
For table-based auth, use the `StargateTableBasedToken` class:
```typescript
const tableBasedToken = new StargateTableBasedToken({authEndpoint: 'http://localhost:8081/v1/auth', username: 'cassandra', password: 'cassandra'});
```
For JWT-based auth, use the `StargateBearerToken` class and pass your token in directly:
```typescript
const bearerToken = new StargateBearerToken('my-token');
```
#### Generating authentication metadata
If you're connecting to Stargate through insecure gRPC credentials - e.g. to a local Stargate instance - you must manually generate metadata for each call, like so:
```typescript
const stargateClient = new StargateClient(grpcEndpoint, grpc.credentials.createInsecure());
const promisifiedClient = promisifyStargateClient(stargateClient);
const authenticationMetadata = await creds.generateMetadata({service_url: 'http://localhost:8081/v1/auth'});
await promisifiedClient.executeQuery(query, authenticationMetadata);
```
This is because the Node gRPC implementation [does not allow composing insecure credentials.](https://github.com/grpc/grpc-node/issues/543)
However, if you're using secure gRPC credentials - e.g. connecting to a remote Stargate instance like an Astra database - you can simply include the token metadata generator when constructing the client. The metadata will automatically be generated on every call to the client:
```typescript
const bearerToken = new StargateBearerToken('my-token');
const credentials = grpc.credentials.combineChannelCredentials(grpc.credentials.createSsl(), bearerToken);
const stargateClient = new StargateClient(grpcEndpoint, credentials);
const promisifiedClient = promisifyStargateClient(stargateClient);
// No need to pass metadata; the credentials passed to the client constructor will do that for us
await promisifiedClient.executeQuery(query);
```
The examples in this README are designed to work with a local Stargate instance, so they use the insecure approach.
### Querying
A simple query can be performed by passing a CQL query to the client:
```typescript
const query = new Query();
query.setCql('select cluster_name from system.local');
// Must manually generate auth metadata if using insecure creds - see authentication section above for details
const authenticationMetadata = await creds.generateMetadata({service_url: 'http://localhost:8081/v1/auth'});
await promisifiedClient.executeQuery(query, authenticationMetadata);
```
Data definition (DDL) queries are supported in the same manner:
```typescript
// Create a new keyspace
const createKeyspaceStatement = new Query();
createKeyspaceStatement.setCql("CREATE KEYSPACE IF NOT EXISTS ks1 WITH REPLICATION = {'class' : 'SimpleStrategy', 'replication_factor' : 1};");
await promisifiedClient.executeQuery(query, authenticationMetadata);
// Create a new table
const createTableStatement = new Query();
createTableStatement.setCql("CREATE TABLE IF NOT EXISTS ks1.tbl2 (key text PRIMARY KEY,value text);");
await promisifiedClient.executeQuery(query, authenticationMetadata);
```
Parameterized queries are also supported:
```typescript
const query = new Query();
query.setCql("select * from system_schema.keyspaces where keyspace_name = ?");
const keyspaceNameValue = new Value();
keyspaceNameValue.setString("system");
const queryValues = new Values();
queryValues.setValuesList([keyspaceNameValue]);
query.setValues(queryValues);
const queryParameters = new QueryParameters();
queryParameters.setTracing(false);
queryParameters.setSkipMetadata(false);
query.setParameters(queryParameters);
const response = await promisifiedClient.executeQuery(query, metadata);
```
If you would like to use a [batch statement](https://cassandra.apache.org/doc/latest/cassandra/cql/dml.html#batch_statement), the client also provides an `executeBatch()` function for this purpose:
```typescript
const insertOne = new BatchQuery();
const insertTwo = new BatchQuery();
insertOne.setCql(`INSERT INTO ${KEYSPACE}.test (key, value) VALUES('a', 1)`);
insertTwo.setCql(`INSERT INTO ${KEYSPACE}.test (key, value) VALUES('b', 2)`);
const batch = new Batch();
batch.setQueriesList([insertOne, insertTwo]);
await promisifiedClient.executeBatch(batch, authenticationMetadata);
```
### Processing the result set
After executing a query a response will be returned containing rows for a SELECT statement, otherwise the returned payload will be unset. You can call `getResultSet()` on the response to grab a ResultSet that's easier to work with. Note the function can return `undefined` if no ResultSet was returned, so you'll need to check it's defined or cast it.
```typescript
// Insert a record into the table
const insert = new Query();
insert.setCql("INSERT INTO ks1.tbl2 (key, value) VALUES ('a', 'alpha');");
await promisifiedClient.executeQuery(insert, authenticationMetadata);
// Read the data back out
const read = new Query();
read.setCql("SELECT key, value FROM ks1.tbl2");
const result = await promisifiedClient.executeQuery(read, authenticationMetadata);
const resultSet = result.getResultSet();
if (resultSet) {
const firstRow = resultSet.getRowsList()[0];
// We call getString() here because we know the type being returned. See below for details on working with types.
const key = firstRow.getValuesList()[0].getString();
console.log(`key: ${key}`);
}
```
#### Reading primitive values
Individual values from queries will be returned as a `Value` object. These objects have boolean `hasX()` methods, where X is the possible type of a value.
There are corresponding `getX()` methods on the `Value` type that will return the value, if present. If the value does not represent type X, calling `getX()` will not throw an error. You'll get `undefined` or another falsy value based on the expected data type.
```typescript
const firstValueInRow = row.getValuesList()[0]; // Assume we know this is a string
const isString = firstValueInRow.hasString(); // will resolve to true
const stringValue = firstValueInRow.getString(); // will resolve to the string value
const isInt = firstValueInRow.hasInt(); // false
const intValue = firstValueInRow.getInt(); // 0 - zero value for this data type
```
#### Reading CQL data types
The built-in `toX()` methods for `Value`s representing more complicated types like UUIDs can be hard to work with. This library exposes helper functions to translate a `Value` into a more easily used type:
- `toUUIDString`
- `toCQLTime`
Unlike the built-in `toX()` methods, these helper functions _will_ throw an error if the conversion fails.
Here's an example of processing a UUID:
```typescript
const insert = new Query();
insert.setCql("INSERT INTO ks1.tbl2 (id) VALUES (f066f76d-5e96-4b52-8d8a-0f51387df76b);");
await promisifiedClient.executeQuery(insert, authenticationMetadata);
// Read the data back out
const read = new Query();
read.setCql("SELECT id FROM ks1.tbl2");
const result = await promisifiedClient.executeQuery(read, authenticationMetadata);
const resultSet = result.getResultSet();
if (resultSet) {
const firstRow = resultSet.getRowsList()[0];
const idValue = firstRow.getValuesList()[0];
try {
const uuidAsString = toUUIDString(idValue);
console.log(`UUID: ${uuidAsString}`);
} catch (e) {
console.error(`Conversion of Value to UUID string failed: ${e}`);
}
}
```
### Promise support
The Node gRPC implementation uses callbacks by default. If you'd prefer promises, this library provides a utility function to create a promisified version of the Stargate gRPC client. The promise will reject if an error occurs:
```typescript
import {
StargateClient,
promisifyStargateClient,
} from "@stargate-oss/stargate-grpc-node-client";
const stargateClient = new StargateClient(
"localhost:8090",
grpc.credentials.createInsecure()
);
const promisifiedClient = promisifyStargateClient(stargateClient);
try {
const queryResult = await promisifiedClient.executeQuery(
query,
metadata,
callOptions
);
const batchResult = await promisifiedClient.executeBatch(
query,
metadata,
callOptions
);
} catch (e) {
// something went wrong
}
```
The `metadata` and `callOptions` arguments are both optional.
### Example uses
See the integration tests at `src/client/client.test.ts` for more example uses of this client. The [DEV_GUIDE.md](DEV_GUIDE.md) has instructions on how to run the integration tests locally as well.
## Issue Management
You can reference the [CONTRIBUTING.md](CONTRIBUTING.md) and [DEV_GUIDE.md](DEV_GUIDE.md) for a full description of how to get involved but the short of it is below.
- If you've found a bug (use the bug label) or want to request a new feature (use the enhancement label), file a GitHub issue
- If you're not sure about it or want to chat, reach out on our [Discord](https://discord.gg/GravUqY)
- If you want to write some user docs 🎉 head over to the [stargate/docs](https://github.com/stargate/docs) repo, Pull Requests accepted!