Skip to content

Commit

Permalink
Merge pull request #61 from firstbatchxyz/erhant/examples
Browse files Browse the repository at this point in the history
Added micro example
  • Loading branch information
anilaltuner authored Dec 15, 2023
2 parents 2d1009c + 79218d2 commit a6a6bf3
Show file tree
Hide file tree
Showing 29 changed files with 7,404 additions and 10 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,13 @@ Depending on your use-cases, we have several optional dependencies:

You can read the full documentation of HollowDB at <https://docs.hollowdb.xyz>. If you are interested in customizing the smart contract of HollowDB and extending its SDKs, refer to this [README](./src/contracts/README.md).

## Examples

Check out the [examples](./examples/) folder for a few examples of HollowDB usage:

- **Simple**: a single JS file that demonstrates getting & setting a key.
- **Micro**: a Vercel Micro backend that can serves HollowDB as API endpoints, useful when you want to use HollowDB from another language.

## Testing

You can run all tests via:
Expand Down
11 changes: 11 additions & 0 deletions examples/micro/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Contract to connect to
CONTRACT_TXID=<your-contract>

# Path to the Arweave wallet
WALLET_PATH=<./path/to/wallet.json>

# Redis URL for the caching layer
REDIS_URL=<redis-url>

# Warp's log level
WARP_LOG_LEVEL=info
14 changes: 14 additions & 0 deletions examples/micro/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
node_modules
build
cache
logs

dump.rdb

.vscode
.DS_Store

!src/cache

.env
tmp.md
7 changes: 7 additions & 0 deletions examples/micro/.mocharc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extension": ["ts"],
"require": "ts-node/register",
"spec": "test/**/*.ts",
"timeout": 100000,
"exit": true
}
168 changes: 168 additions & 0 deletions examples/micro/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
# HollowDB Micro

This backend implementation uses [Micro](https://github.com/vercel/micro) which is a very lightweight server for NodeJS by Vercel, tailored towards usage within a container & supports Unix Domain Socket. It supports almost the CRUD operations as exposed by HollowDB.

## Usage

First, you need an Arweave wallet. Provide the path to the wallet with the `WALLET_PATH` environment variable.

> [!NOTE]
>
> By convention you can put your wallet under the `config` folder as `wallet.json`, which is where the server will look for if no path is specified:
>
> ```sh
> cat your-wallet.json > ./config/wallet.json
> ```
Then, install the packages.
```sh
yarn install
```
Finally, start the server with:

```sh
CONTRACT_TXID="your-contract" yarn start
```

### Configurations

There are several environment variables to configure the server. You can provide them within the command line, or via `.env` file. An example is given [here](./.env.example).

- `WALLET_PATH=path/to/wallet.json` <br> HollowDB requires an Arweave wallet, specified by this variable. If none is given, it defaults to `./config/wallet.json`.

- `REDIS_URL=<redis-url>` <br> You need a Redis server running before you start the server, the URL to the server can be provided with a `REDIS_URL` environment variable. The connection URL defaults to `redis://default:redispw@localhost:6379`.

- `WARP_LOG_LEVEL=<log-level>` <br> By default Warp will log at `info` level, but you can change it via the `WARP_LOG_LEVEL` environment variable. Options are the known levels of `debug`, `error`, `fatal`, `info`, `none`, `silly`, `trace` and `warn`.

### Changing the Address

The listened address defaults to `0.0.0.0:3000`, and this can be overridden via `-l` option.

```sh
# listen to another TCP endpoint
yarn start -l tcp://hostname:port

# listen to UNIX Domain Socket (more performant)
yarn start -l unix:/path/to/socket.sock
```

## Endpoints

Due to how tiny [Micro](https://github.com/vercel/micro) is, it does not come with routing; so we instead provide the `route` within the POST body. The interface for the POST body, along with the interface of returned data if there is one, is provided below.

- [`GET`](#get)
- [`GET_MANY`](#get_many)
- [`PUT`](#put)
- [`PUT_MANY`](#put_many)
- [`UPDATE`](#update)
- [`REMOVE`](#remove)
- [`STATE`](#state)

### `GET`

```ts
interface {
route: "GET",
data: {
key: string
}
}

// response body
interface {
value: any
}
```

Returns the value at the given key.

> [!TIP]
>
> Alternatively, any HTTP GET request with a non-empty URI is treated as a key query, where the URI represents the key. For example, a GET request at `http://localhost:3000/key-name` returns the value stored at key `key-name`.
### `GET_MANY`

```ts
interface {
route: "GET_MANY",
data: {
keys: string[]
}
}

// response body
interface {
values: any[]
}
```

Returns the values at the given `keys`.

### `PUT`

```ts
interface {
route: "PUT",
data: {
key: string,
value: any
}
}
```

Puts `value` at the given `key`. The key must not exist already, or it must have `null` stored at it.

### `PUT_MANY`

```ts
interface {
route: "PUT_MANY",
data: {
keys: string[],
values: any[]
}
}
```

Updates given `keys` with the provided `values`. No key must exist already in the database.

### `UPDATE`

```ts
interface {
route: "UPDATE",
data: {
key: string,
value: any,
proof?: object
}
}
```

Updates a `key` with the provided `value` and an optional `proof`.

### `REMOVE`

```ts
interface {
route: "REMOVE",
data: {
key: string,
proof?: object
}
}
```

Removes the value at `key`, along with an optional `proof`.

### `STATE`

```ts
interface {
route: "STATE"
}
```

Syncs & fetches the latest contract state, and returns it.
2 changes: 2 additions & 0 deletions examples/micro/config/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# hide yo wallet
*.json
44 changes: 44 additions & 0 deletions examples/micro/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
{
"name": "hollowdb-examples-micro",
"author": "FirstBatch Team <[email protected]>",
"private": true,
"contributors": [
"Erhan Tezcan <[email protected]>"
],
"dependencies": {
"axios": "^1.6.2",
"dotenv": "^16.3.1",
"hollowdb": "^1.3.2",
"http-status-codes": "^2.3.0",
"ioredis": "^5.3.2",
"micro": "^10.0.1",
"warp-contracts": "^1.4.28",
"warp-contracts-redis": "^0.3.4"
},
"main": "build/src/index.js",
"scripts": {
"precompile": "yarn clean",
"compile": "npx tsc",
"prestart": "yarn compile",
"start": "npx micro",
"test": "npx mocha --bail",
"clean": "rm -rf ./build"
},
"devDependencies": {
"@types/chai": "^4.3.5",
"@types/mocha": "^10.0.1",
"@types/node": "^18.16.3",
"@types/test-listen": "^1.1.0",
"arlocal": "^1.1.60",
"chai": "^4.3.7",
"hollowdb-prover": "^0.1.4",
"mocha": "^10.2.0",
"test-listen": "^1.1.0",
"ts-node": "^10.9.1",
"typescript": "^5.0.4",
"warp-contracts-plugin-deploy": "^1.0.8"
},
"prettier": {
"printWidth": 120
}
}
56 changes: 56 additions & 0 deletions examples/micro/src/cache/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { CacheOptions } from "warp-contracts";
import { RedisCache, RedisOptions } from "warp-contracts-redis";
import { Redis } from "ioredis";
import config from "../config";
import { CacheTypes } from "../types";

/**
* Utility to create Warp Redis caches.
*
* @param contractTxId contract transaction id to be used as prefix in the keys
* @param client optional client to used a self-managed cache, i.e. you are responsible from
* opening and closing the client.
* @returns caches
*/
export function createCaches(contractTxId: string, client?: Redis): CacheTypes<RedisCache> {
const defaultCacheOptions: CacheOptions = {
inMemory: true,
subLevelSeparator: "|",
dbLocation: "redis.micro",
};

// if a client exists, use it; otherwise connect via URL
const redisOptions: RedisOptions = client ? { client } : { url: config.REDIS_URL };

return {
state: new RedisCache(
{
...defaultCacheOptions,
dbLocation: `${contractTxId}.state`,
},
redisOptions
),
contract: new RedisCache(
{
...defaultCacheOptions,
dbLocation: `${contractTxId}.contract`,
},
redisOptions
),
src: new RedisCache(
{
...defaultCacheOptions,
dbLocation: `${contractTxId}.src`,
},
redisOptions
),
kvFactory: (contractTxId: string) =>
new RedisCache(
{
...defaultCacheOptions,
dbLocation: `${contractTxId}.kv`,
},
redisOptions
),
};
}
12 changes: 12 additions & 0 deletions examples/micro/src/config/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { LoggerFactory } from "warp-contracts";

export default {
/** Redis URL to connect to. Defaults to `redis://default:redispw@localhost:6379`. */
REDIS_URL: process.env.REDIS_URL || "redis://default:redispw@localhost:6379",
/** Path to Arweave wallet. */
WALLET_PATH: process.env.WALLET_PATH || "./config/wallet.json",
/** Log level for underlying Warp. */
WARP_LOG_LEVEL: (process.env.WARP_LOG_LEVEL || "info") as Parameters<typeof LoggerFactory.INST.logLevel>[0],
/** Arweave port for `arlocal`. */
ARWEAVE_PORT: 3169,
} as const;
27 changes: 27 additions & 0 deletions examples/micro/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
// load env variables (put this at top)
import "dotenv/config";

import { Redis } from "ioredis";
import { readFileSync } from "fs";
import { SDK } from "hollowdb";
import type { JWKInterface } from "warp-contracts";

import makeServer from "./server";
import config from "./config";
import { makeWarp } from "./util";
import { createCaches } from "./cache";

const contractTxId = process.env.CONTRACT_TXID;
if (!contractTxId) {
throw new Error("Please provide CONTRACT_TXID environment variable.");
}
const redisClient = new Redis(config.REDIS_URL, {
lazyConnect: false, // explicitly connect
});
const caches = createCaches(contractTxId, redisClient);
const wallet = JSON.parse(readFileSync(config.WALLET_PATH, "utf-8")) as JWKInterface;
const warp = makeWarp(caches);
const hollowdb = new SDK(wallet, contractTxId, warp);

// module.exports needed by Micro
module.exports = makeServer(hollowdb, contractTxId);
Loading

0 comments on commit a6a6bf3

Please sign in to comment.