# Squirrel

> Local mirror mecanism for ETCD

[![npm](https://img.shields.io/npm/v/@coorpacademy/squirrel.svg)](https://www.npmjs.com/package/@coorpacademy/squirrel)
[![Build Status](https://travis-ci.com/CoorpAcademy/squirrel.svg?branch=master)](https://travis-ci.com/CoorpAcademy/squirrel)
[![codecov](https://codecov.io/gh/CoorpAcademy/squirrel/branch/master/graph/badge.svg?token=DhW7y61ov7)](https://codecov.io/gh/CoorpAcademy/squirrel)

Keep a replication of ETCD folder locally for low latency querying.
Provide an index system to access a file without scanning all nodes.

# Summary

- [Install](#install)
- [Usage](#usage)
  - [Node Interface](#node-interface)
    - [`get(path)`](#getpath)
    - [`getBy(index, key)`](#getbyindex-key)
    - [`getAll(index)`](#getallindex)
    - [`set(path, value)`](#setpath-value)
  - [Command Line Interface](#command-line-interface)
    - [`squirrel-sync`](#squirrel-sync)
    - [`squirrel-watch`](#squirrel-watch)
- [Index System](#index-system)
- [Fallback System](#fallback-system)
- [Test](#test)

# Install

```Shell
$ npm install --save @coorpacademy/squirrel
```

```JavaScript
import createSquirrel from '@coorpacademy/squirrel';
```

# Usage

## Node Interface

```JavaScript
const squirrel = createSquirrel({
    hosts: 'http://localhost:2379',
    auth: null,
    ca: null,
    key: null,
    cert: null,

    cwd: '/',
    fallback: '/tmp/squirrel.json',
    indexes: ['foo', 'bar.baz']
});
```

Options:
- `hosts`: ETCD hosts. [more](https://github.com/stianeikeland/node-etcd/#etcdhosts--1270012379-options)
- `auth`: A hash containing `{user: "username", pass: "password"}` for basic auth. [more](https://github.com/stianeikeland/node-etcd/#constructor-options)
- `ca`: Ca certificate. [more](https://github.com/stianeikeland/node-etcd/#constructor-options)
- `key`: Client key. [more](https://github.com/stianeikeland/node-etcd/#constructor-options)
- `cert`: Client certificate. [more](https://github.com/stianeikeland/node-etcd/#constructor-options)
- `cwd`: ETCD current working directory.
- `fallback`: Temporary file to save ETCD backup.
- `indexes`: Array of key to index.

### Methods

Consider the following folder:


```Shell
/
├── bar
│   └── baz   { "bar": { "baz": "qux" } }
└── foo       { "foo": "bar" }
```

#### `get(path)`

Get file by path. Returns `Promise`;

- `path` (String): the path of the file to get.

```JavaScript
const foo = await squirrel.get('/foo');
console.log(foo); // { "foo": "bar" }

const barBaz = await squirrel.get('/bar/baz');
console.log(barBaz); // { "bar": { "baz": "qux" } }
```

#### `getBy(index, key)`

Get by index value. Returns `Promise`;

- `index` (String): the path of the property to get. It needs to be declared in the [`indexes` option](#node-interface)
- `key` (String): the value to match

```JavaScript
const foo = await squirrel.getBy('foo', 'bar');
console.log(foo); // { "foo": "bar" }

const barBaz = await squirrel.getBy('bar.baz', 'qux');
console.log(barBaz); // { "bar": { "baz": "qux" } }
```

Fields can be nested, as described by [`_.get`](https://lodash.com/docs#get).

#### `getAll(index)`

Get index Map. Returns `Promise`;

- `index` (String): the path of the property to get. It needs to be declared in the [`indexes` option](#node-interface)

```JavaScript
const foo = await squirrel.getAll('foo');
console.log(foo); // { "bar": { "foo": "bar" } }

const barBaz = await squirrel.getAll('bar.baz');
console.log(barBaz); // { "qux": { "bar": { "baz": "qux" } } }
```

#### `set(path, value)`

Set file by path. Returns `Promise`;

- `path` (String): the path of the file to get.
- `value` (Object): An object to store in file. Will be serialized.

```JavaScript
const foo = await squirrel.set('/foo',  { "foo": "bar" });
console.log(foo); // { "foo": "bar" }

```


## Command Line Interface

### `squirrel-sync`

Synchronize FS folder with ETCD folder.

```Shell
$ squirrel-sync --hosts localhost:2379 ./fs-folder /etcd-folder
```

### `squirrel-watch`

Watch ETCD folder changes.

```Shell
$ squirrel-watch --hosts localhost:2379 /etcd-folder
```

### `squirrel-dump`

Write ETCD folder in `preloadedStore` format.

```Shell
$ squirrel-dump --hosts localhost:2379 /etcd-folder ./dump.json
```

### Arguments

- `--hosts="host1,host2"`: ETCD hosts. [more](https://github.com/stianeikeland/node-etcd/#etcdhosts--1270012379-options)
- `--ca=/file.ca`: Ca certificate. [more](https://github.com/stianeikeland/node-etcd/#constructor-options)
- `--key=/file.key`: Client key. [more](https://github.com/stianeikeland/node-etcd/#constructor-options)
- `--cert=/file.cert`: Client certificate. [more](https://github.com/stianeikeland/node-etcd/#constructor-options)

# Index System

Squirrel allows to put JSON in file. In this case, it could be indexes to access directly. Consider the following ETCD directory.

```Shell
/
├── file1 { "foo": "bar" }
├── file2 { "foo": "baz" }
└── file3 { "foo": "qux" }
```

First of all, we should indicate Squirrel which paths we want to index.

```JavaScript
const squirrel = createSquirrel({
  indexes: ['foo']
});
```

Now, we can get the contents of `file1` by searching for its `foo` value.

```JavaScript
const file1 = await squirrel.getBy('foo', 'bar');
console.log(file1); // { "foo": "bar" }
```

We can also get the value of the index as an object.

```JavaScript
const fooIndex = await squirrel.getAll('foo');
console.log(fooIndex);
/*
{
  "bar": { "foo": "bar" },
  "baz": { "foo": "baz" },
  "qux": { "foo": "qux" }
}
 */
```

If two files have the same index value, Squirrel keeps one of the two.

Squirrel scans all files, no matter how deep, that contain a JSON value.

Index could be a complex path, as long as it works with [`_.get`](https://lodash.com/docs#get).

# Fallback System

By declaring a `fallback` path, Squirrel is able :
- to save its state every time a change is made
- to restore the state to be faster on the next restart even if ETCD isn't available.

# Test

You may run tests with

```Shell
$ npm test
```

# [Marble](./MARBLE.md)

