# Using the Client SDK

Thank you for checking out the Vendia Client SDK! We’ve been building and testing this client internally for months and enthusiastically encourage you to try it out! The project is still under active development, however, and **the API is subject to change**. Thanks for your patience!

## @vendia/client - a type-safe client for Vendia

The Client SDK is a type-safe **TypeScript & JavaScript** client for your **project**’s API with **auto-generated code customized to match your project’s schema!**

If you’re new to Vendia and wondering what a “ **project**” is? This is a great place to start: [What is a project?](https://docs.vendia.com/platform/vendia-terminology#project).

The official Vendia client is the easiest way to start working with your **project**. Enjoy autocomplete (intellisense) in your favorite IDE, built-in support for both HTTP and websocket GraphQL APIs (see [Realtime Data](https://docs.vendia.com/platform/cli/vendia-client-sdk/#realtime-data-graphql-subscriptions)), multiple authentication methods, file upload/download, and additional conveniences. The client is _[isomorphic](https://en.wikipedia.org/wiki/Isomorphic_JavaScript)_ — it can be used in both the **browser** and **server (node.js)**.

**What does “auto-generated code” mean?**

Code based on your **project**’s schema will be generated automatically during installation (you can read more about how it works [in the appendix below](https://docs.vendia.com/platform/cli/vendia-client-sdk/#code-generation-details)). If your schema included a “product” entity, for example, your generated client would include the following methods:

```javascript
// List all the products
const listProductsResponse = await client.entities.product.list();
// Add a new product
const addProductResponse = await client.entities.product.add({
  name: "super-widget",
  inventory: 100,
});
// Get a product by ID
const getProductResponse = await client.entities.product.get("abc-123");
```

## Getting Started

### Prerequisites

- Create a [Vendia account](https://share.vendia.net/) and deploy a **project** you’d like to use!
- Install the [Vendia CLI](https://docs.vendia.com/platform/cli) (or update via `npm update -g @vendia/share-cli`)

```bash
npm install -g @vendia/share-cli
```

### Step 1: Pulling Your **Project** Schema

We’ll need to store some information about your **project** and its schema locally in order to generate custom, type-safe client code. Then we’ll use the Vendia CLI to authenticate into your **project**, fetch the required data, and store it locally.

_Note: if you’ve already got a `.vendia` directory (e.g. another team member has already committed the `.vendia` directory and schema files to your repo), you can skip this step and continue to [Step 2](https://docs.vendia.com/platform/cli/vendia-client-sdk/#step-2-installing-the-client)_

1. Navigate to the _root directory_ of an existing project you’ve been working on or create a new, empty directory and `cd` into it.
2. Use the following command and follow the prompts to fetch your **project**’s schema data and store it locally:

```bash
share client:pull
```

If the command is successful, you should end up with a `.vendia` directory that looks like this:

```.vendia
├── config.json
├── schema.graphql
└── schema.json
```

This directory can be committed to your repository and shared with others working on the same project.

### Step 2: Installing the Client

Use your favorite npm client to install the Vendia client package:

```bash
npm install @vendia/client
```

This is all you need to do.

Once the installation completes, a post-installation script will run automatically — this script generates custom TypeScript files (and compiles them to JavaScript and type declaration files). This is necessary for the client to work correctly!

If you installed the client _before_ pulling your **project**’s schema, just follow [Step 1](https://docs.vendia.com/platform/cli/vendia-client-sdk/#step-1-pulling-your-project-schema) above to pull with the CLI — you’ll be prompted to run code generation afterwards.

You can also run _just_ code generation with the following command:

```bash
share client:generate
```

#### **Help! I got an error during npm installation!**

Please take a look at [fixes for common issues](https://docs.vendia.com/platform/cli/vendia-client-sdk/#fixes-for-common-issues) below!

## Usage

### Initializing the Client

You can instantiate an instance of the Vendia client using the following code:

```javascript
import { createVendiaClient } from "@vendia/client";
const client = createVendiaClient({
  apiUrl: `<Your GraphQL URL>`,
  websocketUrl: `<Your Websocket URL>`,
});
```

### Options

- `apiUrl` \- string, required
- `websocketUrl` \- string, optional (but required in order to use [GraphQL subscriptions](https://docs.vendia.com/platform/cli/vendia-client-sdk/#realtime-data-graphql-subscriptions))
- `apiKey` \- string, optional
- `debug` \- boolean, optional (set to `true` to enable verbose logging)

### Authentication

The Vendia client currently only supports authentication via **API key**. **More options for authentication coming soon!**

### API Key

The easiest way to get started _in a server-side scenario_ is with your API key — it can be passed in via the `apiKey` option when instantiating the client:

```javascript
const client = createVendiaClient({
  apiUrl: `<Your GraphQL URL>`,
  websocketUrl: `<Your Websocket URL>`,
  apiKey: process.env.VENDIA_API_KEY, // <---- API key
});
```

_Warning: Never expose your API key to untrusted users! API keys should only be used in server-side applications (node.js) and should always be accessed in code via environment variables._

## Reading and Writing Data

### Working with Entities

CRUD operations for each of the top-level data types (known as “entities”) defined in your Vendia JSON schema are available under the `entities` namespace. Entity names are converted to camelCase to conform with [idiomatic JavaScript](https://github.com/rwaldron/idiomatic.js). For example, “CarParts” will be available at `entities.carParts`.

Let’s assume your JSON schema had entities for “Product”, “Shipment”, and “User”. You could perform the following example operations:

```javascript
const { entities } = client;
// Add a new "product"
const productResponse = await entities.product.add({
  name: "super-widget",
  inventory: 100,
});
// List your "shipments"
const shipmentResponse = await entities.shipment.list();
// Get a "user" by id
const userResponse = await entities.user.get("abc-123");
```

### Adding an Item

Adding new items can be performed with the `add` method. This method takes a single argument: an object containing the data to be added. The `add` method returns a promise that resolves to the newly created item.

```javascript
const response = await entities.product.add({
  name: "super-widget",
  inventory: 100,
});
```

Singular entities — that is, entities that are defined in your JSON schema as any type other than "array" — must be created with the `create` method rather than `add`. Once you’ve created a singular entity, you can `update` it to make changes, but you can’t _add_ more than one.

```javascript
const response = await entities.topSellingProductsSummary.create({
  topSellingProductName: "super-widget",
  unitsSoldInLastNinetyDays: 100000,
});
```

### Updating an Item

Updating an item can be performed with the `update` method. This method has one required argument: an object containing the item to be updated which _must_ include the existing item’s `_id`. The `update` method returns a promise that resolves to the updated item.

A second, optional argument can be used to update an item [conditionally](https://docs.vendia.com/platform/cli/vendia-client-sdk/#conditionally-updating-or-removing-items).

```javascript
const response = await entities.product.update({
  _id: existingProduct._id,
  name: "EVEN-MORE-SUPER-widget",
  inventory: 1000000,
});
// Retrieving an item, changing a field, and saving the updated item
const product = await entities.product.get("abc-123");
product.inventory = product.inventory - 1;
const updateProductResponse = await entities.product.update(product);
```

### Removing an Item

Removing an item can be performed with the `remove` method. This method has a single required argument: the `_id` of the item to be removed. The `remove` method returns a promise that resolves to the removed item.

A second, optional argument can be used to remove an item [conditionally](https://docs.vendia.com/platform/cli/vendia-client-sdk/#conditionally-updating-or-removing-items).

```javascript
const response = await entities.product.remove("abc-123");
```

Singular entities - that is, entities that are defined in your JSON schema as any type other than "array" - must be removed with the `delete` method rather than `remove`.

```javascript
const response = await entities.topSellingProductsSummary.delete();
```

### Conditionally Updating or Removing Items

Update and removal mutations can be performed _conditionally_ - that is, the update/removal will only be performed if the item currently stored in the database matches the provided conditions. Conditions are specified via an object passed to the `condition` option. Complex conditions can be specified by recursively nesting additional conditions via the `_and`/_`or` properties.

```javascript
// Only perform this update if the item stored in the database has a name of "super-widget"
const updateResponse = await entities.product.update(
  {
    _id: existingProduct._id,
    name: "EVEN-MORE-SUPER-widget",
    inventory: 1000000,
  },
  {
    condition: {
      name: {
        eq: "super-widget",
      },
    },
  }
);
// Only remove this item if the version currently stored in the database has an inventory of 0 (or less) OR its name is prefixed with "OBSOLETE"
const removeResponse = await entities.product.remove("abc-123", {
  condition: {
    _or: {
      name: {
        beginsWith: "OBSOLETE",
      },
      inventory: {
        le: 0,
      },
    },
  },
});
```

### Listing Items

Listing items can be performed with the `list` method. This method takes a single optional argument: an object containing options to be applied to the list request. The `list` method returns a promise that resolves to an array of items.

```javascript
const response = await entities.product.list();
```

#### Pagination

Pagination is cursor-based — the `list` method will return a list of items along with a `nextToken` cursor that can be used to retrieve the next page of items - `nextToken` will be `null` when there are no more pages to retrieve.

```javascript
const firstPage = await entities.product.list();
const secondPage = await entities.product.list({
  nextToken: firstPage.nextToken,
});
```

The number of items returned in each page can be controlled with the `limit` option (defaults to `50` items).

```javascript
const fiveProductsResponse = await entities.product.list({ limit: 5 });
```

#### Filtering

List queries can be augmented with a powerful filtering syntax passed as an object to the `filter` option.

```javascript
// List all products where name contains 'widget', inventory is greater than 50, and price is less than 100
const response = await entities.product.list({
  filter: {
    name: {
      contains: "widget",
    },
    _and: {
      inventory: {
        gt: 50,
      },
      price: {
        lt: 100,
      },
    },
  },
});
```

### Retrieving an Item

Retrieving an item can be performed with the `get` method. This method has a single required argument: the `_id` of the item to be retrieved. The `get` method returns a promise that resolves to the retrieved item.

```javascript
const response = await entities.product.get("abc-123");
```

#### Retrieving Previous Versions of an Item

Previous versions can be retrieved by passing an optional second argument to the `get` method: an object containing the `version` of the item to be retrieved.

```javascript
const response = await entities.product.get("abc-123", {
  version: 1,
});
```

### Modifying the Default Synchronous Mutation Behavior

Mutations will be performed _synchronously_, by default, meaning the promise returned from calling `add`/`update`/`remove` won’t resolve until the data has been safely stored in your **project** — this is probably the behavior you would expect! Keep in mind, however, that Vendia is a decentralized, ledgered database and consensus amongst all participating **workspaces** in your **project** is required before the data can be immutably ledgered. The consensus process can occasionally add a delay of _up to several seconds_ to mutation requests.

If you don’t want or need to wait for this process to complete, you can use the `syncMode` option with a value of `ASYNC` — this will cause the mutation to be performed asynchronously, and the promise returned from the method will resolve as soon as _your node_ has received the request. You’ll be provided a `transactionId` that can be used to check the status of the mutation later along with the `_id` of the item you’re adding/updating/removing.

```javascript
const response = await entities.product.add(
  {
    name: "super-widget",
    inventory: 100,
  },
  {
    syncMode: "ASYNC",
  }
);
console.log(response?.transaction?.transactionId);
console.log(response?.transaction?._id);
```

## Working with ACLs

Vendia allows you to control read and write access to the data in your **project** via access control lists (ACLs). ACLs can be powerful, but are completely optional — [you can read more about ACLs here](https://docs.vendia.com/platform/operational/secure-data-sharing/fine-grained-data-permissions).

### Adding ACLs to Mutations

ACLs can be passed to `add`/`update` mutations via the `aclInput` option. Note that this option will only be available for entities that have [ACLs enabled in your **project**’s schema](https://docs.vendia.com/platform/operational/secure-data-sharing/fine-grained-data-permissions#schema-definition-requirement).

```javascript
// Add an item with an ACL specifying that all workspaces (aside from you) are restricted to read-only access
const response = await entities.product.add(
  {
    name: "read-only-widget",
    inventory: 100,
  },
  {
    aclInput: {
      acl: [
        {
          principal: {
            nodes: ["*"],
          },
          operations: ["READ"],
        },
      ],
    },
  }
);
```

## Realtime Data (GraphQL Subscriptions)

The client makes it easy to use GraphQL subscriptions to respond to data updates in realtime. Changes to `entities`, `blocks`, `files`, `settings`, and more can all be subscribed to using the following format:

```javascript
const { entities } = client;
entities.product.onAdd((data) => {
  alert(`A new product named ${data.result.name} been added!`);
});
entities.product.onUpdate((data) => {
  alert(`An existing product named ${data.result.name} has been updated!`);
});
entities.product.onRemove((data) => {
  alert(`An existing product named ${data.result.name} has been removed!`);
});
```

Non-entity types such as `blocks` and `files` follow the same format:

```javascript
const { blocks, files } = client;
blocks.onAdd((data) => {
  alert(`Block ${data.result.blockId} has been minted!`);
});
files.onUpdate((data) => {
  alert(`${data.result.destinationKey} has changed!`);
});
```

Subscriptions return an unsubscribe method which can be used to terminate the subscription:

```javascript
const unsubscribe = entities.product.onAdd((data) => console.log(data));
// No longer interested!
unsubscribe();
```

## Storage

File/folder operations are accessed via the `files` and `folders` namespaces located under the `storage` namespace. [Learn more about Vendia file storage.](https://docs.vendia.com/platform/files-and-folders)

The client currently supports copying files from existing S3 buckets and retrieving metadata about files on your **project**.

_**Coming soon: support for directly uploading and retrieving files to your project from the client!**_

```javascript
const { storage } = client;
const documentsFolder = "documents";
const addFolderResponse = await storage.folders.add({
  name: documentsFolder,
});
const addFileResponse = await storage.files.add({
  destinationKey: `${documentsFolder}/my-document.txt`,
  sourceKey: "my-document.txt",
  sourceBucket: "my-bucket",
  sourceRegion: "us-east-1",
});
const getFileResponse = await storage.files.get(addFileResponse._id);
console.log(`My document is available at ${getFileResponse.destinationKey}!`);
```

## Blocks

The entire history of your **project** is available via the `blocks` namespace. Blocks can be accessed via `get` or `list` operations, and the `onAdd` subscription can be used to react to newly minted blocks in realtime. [Learn more about “blocks” and other Vendia terminology here](https://docs.vendia.com/platform/vendia-terminology#block).

```javascript
const { blocks } = client;
const getResponse = await blocks.get("example-block-id-abc-123");
const listResponse = await client.blocks.list();
```

## Settings

Various settings can be queried and updated using the `settings` namespace (e.g., auth, success/error notifications). Use the `get` and `update` operations to retrieve and update settings. The `onUpdate` subscription can be used to react to settings changes in realtime.

```javascript
const response = await client.settings.get();
```

## Uni Info

The `uniInfo` namespace provides access to information about your **project** (e.g., its name, schema, and info about each participating **workspace** in the **project**). Use the `get` operation (no arguments required) to retrieve the info and the `onUpdate` subscription to react to changes in realtime.

```javascript
const response = await client.uniInfo.get();
```

## Smart Contracts

You can use the `smartContracts` namespace to interact with your **project**’s smart contracts. Use the `add`, `get`, `list`, `update`, and `remove` operations to perform CRUD operations on smart contracts.

```javascript
const { contracts } = client;
const response = await contracts.add({
  name: "update-delivery-status",
  resource: {
    uri: "arn:aws:lambda:us-west-2:123456789012:function:ContractEnforcement:9",
  },
  description:
    "a smart contract that updates the delivery status of a shipment",
  inputQuery:
    "query shipmentDetails($id: ID!) { getShipment(id: $id) { _id orderId destinationWarehouse }}",
  outputMutation:
    "mutation m($id: ID!, $delivered: Boolean, $lastUpdated: String, $orderId: String) { updateShipment(id: $id, input: { delivered: $delivered, lastUpdated: $lastUpdated, orderId: $orderId }, syncMode: ASYNC) { transaction { _id } } }",
});
```

Smart contracts can be invoked with the `invoke` method.

```javascript
const response = await contracts.invoke("example-contract-id-abc-123");
```

You can also use the `onAdd`, `onUpdate`, and `onRemove` subscriptions to react to changes in realtime. [Read all about Smart Contracts here](https://docs.vendia.com/platform/limited-availability/smart-contracts).

## Custom GraphQL Requests

It is possible to make custom GraphQL requests via the `request` method. This shouldn’t be necessary often, but there are a few scenarios that the client does not directly support where custom requests might prove useful:

1. Requesting a subset of fields — the client always requests _every_ field of a given data type which may be more data than you need.
2. Executing multiple queries or mutations in a single request (also known as “batching”).
3. Executing [Vendia Transactions](https://docs.vendia.com/platform/operational/scalar-data/vendia-transactions), which require batching mutations in a single request, and additionally guarantee that mutations will be performed _serially, in order, as an atomic unit_.

### Requesting a Subset of Fields

```javascript
// Requesting a subset of fields on the Vendia Block type
const listBlocksQuery = `
  query listBlocks {
    listVendia_BlockItems {
      Vendia_BlockItems {
        blockId
      }
      nextToken
    }
  }
`;  
const response = await client.request(listBlocksQuery);
// Returns the full GraphQL "data" response
console.log(response?.listVendia_BlockItems?.Vendia_BlockItems?.[0]?.blockId);
```

### Executing a Vendia Transaction

```javascript
const vendiaTransactionQuery = `
mutation exampleMutation @vendia_transaction { # <-- vendia_transaction directive
  update_Product(
    id: "abc-123"
    input: { inventory: 99 }
    syncMode: NODE_LEDGERED
  ) {
    result {
      inventory
    }
  }
  update_User(
    id: "def-456"
    input: { products_purchased : 2 }
    syncMode: NODE_LEDGERED
  ) {
    result {
      products_purchased
    }
    transaction {
      transactionId
    }
  }
}
`;  
const response = await client.request(vendiaTransactionQuery);
console.log(response?.update_User?.transaction?.transactionId);
```

## Fixes for Common Issues

**Issue: Received `Error: Cannot find module '../../.vendia-client/index'` when trying to build or run my code.**

This error means that code generation did not complete successfully. The most likely cause is that the `.vendia` folder is missing from the root of your project. Please follow the instruction above for [pulling your **project**’s schema](https://docs.vendia.com/platform/cli/vendia-client-sdk/#step-1-pulling-your-project-schema).

**Issue: I received an error during _installation_ or _code generation_.**

Potential causes include the following:

1. The `.vendia` folder is missing from the root of your project.
2. If you’ve updated **@vendia/client** to a new version which takes advantage of changes/additions to the core Vendia platform, you may need to pull the latest version of your **project**’s schema. In this case, it’s the generated GraphQL schema rather than your JSON schema that may be out of date. _This should only happen when bumping major versions of the client, but we’re still in alpha at the moment and moving very quickly!_

In either case, please follow the instruction above for [pulling the latest version of your **project**’s schema](https://docs.vendia.com/platform/cli/vendia-client-sdk/#step-1-pulling-your-project-schema) and follow the CLI prompts to execute code generation afterwards.

**Issue: I’m using `pnpm` to install dependencies and getting code-gen or typescript compilation errors**

`pnpm` uses symlinks in node _modules to enable some dependency management optimizations. Unfortunately, these symlinks can confuse TypeScript’s compiler (`tsc`). If you’re using `pnpm` and running into issues, you can either try using `npm` instead, _or_ you can add an `.npmrc` file to the root of your project with the following line:

```text
node-linker=hoisted
```

This disables some of `pnpm`’s optimizations, but should resolve the issue.

**Issue: `client.entities` is an empty object! It doesn’t contain any of the entities described by my project’s JSON schema.**

Again, the most likely cause is that the `.vendia` folder is missing from the root of your project.
If the post-installation script cannot locate the `.vendia` folder, it will fall back to using a generic Vendia schema that doesn’t contain any of the entities described by your **project**’s JSON schema — this results in an empty `client.entities` namespace (though all other aspects of client functionality should work). Please follow the instruction above for [pulling your **project**’s schema](https://docs.vendia.com/platform/cli/vendia-client-sdk/#step-1-pulling-your-project-schema).

**Issue: code completion (intellisense) doesn’t work in WebStorm IDE (JavaScript projects only - should always work in TypeScript projects).**

Workaround: Open `preferences` → `languages and frameworks` → `javascript` → `libraries` → `add` → `+ button` → use `command + shift + .` to show hidden folders (on Mac, not sure about Windows - apologies), select `.vendia-client` directory. This adds all the types to the project.

## Appendix

### Schema Evolution

Vendia allows you to evolve your schema as your data sharing requirements change — you can read more about schema evolution [in the user guide](https://docs.vendia.com/platform/operational/modeling/schema-evolution) and [in this blog post](https://www.vendia.com/blog/schema-evolution). Whenever you evolve your schema, you’ll need to update the schema files stored in your `.vendia` directory and generate new client code.

Use the Vendia CLI to run

```bash
share client:pull
```

This will pull the latest schema files to your `.vendia` directory and then issue the following prompt:
```
Would you like to update the auto-generated client code based on the latest schema?
This is highly recommended. (Y/n)
```

Tap `Enter` to continue and you’re done!

### Code Generation Details

The `@vendia/client` package consists of a lightweight wrapper (the exported `createVendiaClient` function) along with a suite of tools used to dynamically generate TypeScript files based on your schema. When the package is installed/updated via npm/yarn/pnpm, a post-installation script will attempt to perform the following steps:

1. Find the `.vendia` directory in the root of your project and read the schema/config files inside.
2. Use the schema/config data to generate TypeScript code tailored to your **project**’s schema.
3. Create a `.vendia-client` directory inside `node_modules`
4. Copy the wrapper TypeScript source code (`createVendiaClient`) and into `node_modules/.vendia-client`.
5. Write the generated TypeScript code to `node_modules/.vendia-client/generated.ts`.
6. Compile the TypeScript code to JavaScript and type declaration (* .d.ts) files.

If there is no `.vendia` directory, a generic Vendia schema that doesn’t contain any “entities” (the data types defined by your **project**’s JSON schema) will be used, resulting in an empty `client.entities` namespace (though all other aspects of client functionality should work).

This project was heavily inspired by [Prisma’s awesome type-safe client](https://www.prisma.io/client) and made possible by a suite of amazing open-source tools especially the incredible [GraphQL Code Generator](https://www.graphql-code-generator.com/)!

### FAQ

**Why is the `.vendia-client` directory created in `node_modules`?**

There are a few reasons for this:

1. This allows for a simpler installation process with very little configuration.
2. It allows the client to be imported from a consistent location (e.g., `import { createVendiaClient } from '@vendia/client'`).
3. We would prefer not to surprise anyone by modifying files outside of `node_modules` during a package installation.

**Can I specify an alternate path for the generated files?**

Not at this time. Please let us know if you need this!
