New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

sloki

Package Overview
Dependencies
Maintainers
1
Versions
6
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

sloki - npm Package Compare versions

Comparing version 0.0.6 to 0.0.7

src/cli/run.js

48

CHANGELOG.md
# Changelog
## unreleased
## [0.0.7](https://github.com/sloki-project/sloki/milestone/5) - UNRELEASED
### Added
* add method `versions` [#19](https://github.com/sloki-project/sloki/issues/19)
* add alias property for all methods [#21](https://github.com/sloki-project/sloki/issues/21)
* add binary tcp protocol [#22](https://github.com/sloki-project/sloki/issues/22)
* add manual garbage collector [#23](https://github.com/sloki-project/sloki/issues/23) [#24](https://github.com/sloki-project/sloki/issues/24)
* TLS: generate self signed certificate when sloki start [#26](https://github.com/sloki-project/sloki/issues/26)
* TLS: implement both jsonrpc and binary server [#27](https://github.com/sloki-project/sloki/issues/27)
### Changed
* switch to named parameter in jsonrpc layer [#20](https://github.com/sloki-project/sloki/issues/20)
* no response needed if attribute "nr" exists in params (lazy mode)
* listen on multiple port per transport/protocol by default [#25](https://github.com/sloki-project/sloki/issues/25)
## [0.0.6](https://github.com/sloki-project/sloki/milestone/4) - 2019-02-09
### Added
* add method wait for testing purpuse [(one sec)](https://github.com/sloki-project/sloki/commit/80c51ac81d18e18794f1782486aec9d8b4166c55)
* add method `wait` for testing purpose [(one sec)](https://github.com/sloki-project/sloki/commit/80c51ac81d18e18794f1782486aec9d8b4166c55)

@@ -25,6 +33,6 @@ ### Changed

### Added
* add method collection/update [#5](https://github.com/sloki-project/sloki/issues/5)
* add method collection/find [#3](https://github.com/sloki-project/sloki/issues/3)
* add method collection/remove [#4](https://github.com/sloki-project/sloki/issues/4)
* add method collection/get [#2](https://github.com/sloki-project/sloki/issues/2)
* add method collection/`update` [#5](https://github.com/sloki-project/sloki/issues/5)
* add method collection/`find` [#3](https://github.com/sloki-project/sloki/issues/3)
* add method collection/`remove` [#4](https://github.com/sloki-project/sloki/issues/4)
* add method collection/`get` [#2](https://github.com/sloki-project/sloki/issues/2)

@@ -44,15 +52,15 @@ ### Changed

* add JSONRPC over tcp layer (jayson)
* add method server/clients
* add method server/methods
* add method server/maxClients
* add method server/quit
* add method server/shutdown
* add method database/db
* add method database/loadDatabase
* add method database/listDatabases
* add method database/saveDatabase
* add method database/addCollection
* add method database/listCollections
* add method collection/get
* add method collection/insert
* add method server/`clients`
* add method server/`methods`
* add method server/`maxClients`
* add method server/`quit`
* add method server/`shutdown`
* add method database/`db`
* add method database/`loadDatabase`
* add method database/`listDatabases`
* add method database/`saveDatabase`
* add method database/`addCollection`
* add method database/`listCollections`
* add method collection/`get`
* add method collection/`insert`
* add tests

@@ -59,0 +67,0 @@

{
"name": "sloki",
"version": "0.0.6",
"version": "0.0.7",
"description": "A NodeJS server for LokiJS",
"main": "index.js",
"scripts": {
"test": "node test/index.js"
"test": "node --optimize_for_size --expose_gc test/index.js",
"start": "node --optimize_for_size --max_old_space_size=7360 --expose_gc index.js"
},

@@ -30,18 +31,17 @@ "bin": {

"dependencies": {
"abrequire": "0.0.4",
"evillogger": "^1.2.2",
"fs-extra": "^7.0.1",
"jayson": "^2.1.1",
"klaw-sync": "^6.0.0",
"async": "2.6.1",
"evillogger": "1.2.2",
"fs-extra": "7.0.1",
"jayson": "2.1.2",
"klaw-sync": "6.0.0",
"lokijs": "franck34/LokiJS",
"pretty-bytes": "^5.1.0",
"sloki-node-client": "0.0.6"
"missive": "3.0.1",
"pretty-bytes": "5.1.0",
"selfsigned": "1.10.4"
},
"devDependencies": {
"async": "^2.6.1",
"eslint": "^5.13.0",
"tap": "^12.1.1",
"tap-spec": "^5.0.0",
"tape": "^4.9.2"
"eslint": "5.13.0",
"tap-spec": "5.0.0",
"tape": "4.9.2"
}
}
# Sloki (WORK IN PROGRESS)
A NodeJS Server for [LokiJS](http://lokijs.org/)
[![Join the chat at https://gitter.im/sloki-server/community](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/sloki-server/community)
[![alt CI-badge](https://travis-ci.org/sloki-project/sloki.svg?branch=master)](https://travis-ci.org/sloki-project/sloki)
[![npm version](https://badge.fury.io/js/sloki.svg?v=0)](http://badge.fury.io/js/sloki)
[![Known Vulnerabilities](https://snyk.io/test/github/sloki-project/sloki/badge.svg?targetFile=package.json)](https://snyk.io/test/github/sloki-project/sloki?targetFile=package.json)
[![Dependencies](https://david-dm.org/sloki-project/sloki.svg)](https://david-dm.org/sloki-project/sloki)
[![Dev Dependencies](https://david-dm.org/sloki-project/sloki/dev-status.svg)](https://david-dm.org/sloki-project/sloki?type=dev)
[![Known Vulnerabilities](https://snyk.io/test/github/sloki-project/sloki/badge.svg?targetFile=server/package.json)](https://snyk.io/test/github/sloki-project/sloki?targetFile=server/package.json)
[![Dependencies](https://david-dm.org/sloki-project/sloki.svg?path=server/)](https://david-dm.org/sloki-project/sloki)
[![Dev Dependencies](https://david-dm.org/sloki-project/sloki/dev-status.svg?path=server/)](https://david-dm.org/sloki-project/sloki?type=dev)
-----
## Documentation
1. [Introduction](#1-introduction)
1. [Transports](#1i-transports)
2. [Protocols](#1ii-protocols)
1. [Binary](#1iia-binary-protocol-default) (default)
2. [JSONRPC](#1iib-jsonrpc)
2. [Installation](#4-installation)
-----
## Overview
## 1. Introduction
Sloki make LokiJS ***scalable***.
Sloki is a nodejs server which embed [LokiJS](http://lokijs.org/), a blazing fast in-memory documents database.
Sloki help to make LokiJS ***scalable*** : you can now have multiple processes speaking with LokiJS through Sloki.
* It embed [LokiJS](http://lokijs.org/)
* It expose a [JSONRPC](https://www.jsonrpc.org/) API, thanks to [Jayson](https://github.com/tedeh/jayson)
* It **WILL** support TCP/TLS
* It **MAY** support HTTP/HTTPS
A possible architecture using sloki :
```
+----------------------------+ TCP / Binary +-----------------------------------+
| NodeJS app worker #1 |<------------------->| |
+----------------------------+ | Sloki |
| |
+----------------------------+ TCP / Binary | +-------------------------+ |
| NodeJS app worker #2 |<------------------->| | | |
+----------------------------+ | | | |
| | LokiJS | |
+----------------------------+ TCP / JSONRPC | | fast in-memory database | |
| go/php/python/C/whatever |<------------------->| | | |
+----------------------------+ | | | |
| | | |
+----------------------------+ TCP / Binary | +-------------------------+ |
| sloki-cli |<------------------->| |
+----------------------------+ +-----------------------------------+
```
JSONRPC (jayson)
TCP|TLS|HTTP|HTTPS
+----------------------------+ +----------------------------------+
| | | sloki |
| NodeJS Daemon |<----------------------->| (Local or Remote) |
| | | |
+----------------------------+ | +------------------------+ |
| | | |
+----------------------------+ | | | |
| | | | | |
| NodeJS Daemon |<----------------------->| | LokiJS | |
| | | | (database) | |
+----------------------------+ | | | |
| | | |
+----------------------------+ | +------------------------+ |
| | | |
| CLI |<----------------------->| |
| | | |
+----------------------------+ +----------------------------------+
## 1.i. Transports
For moment, only TCP transport is supported. The advantage of TCP vs HTTP API is that the connection is persistent.
By default, Sloki listens on the following ports:
| Port | Transport | TLS | Protocol | ops/sec/client
|:---------:|------------|------|------------------|------------
| 6370 | TCP | NO | Binary (fastest) | avg 18K ops/sec
| 6371 | TCP | YES | Binary (fastest) | avg 25K ops/sec (??)
| 6372 | TCP | NO | JSONRPC | avg 17K ops/sec
| 6373 | TCP | YES | JSONRPC | avg 24K ops/sec (??)
If somebody have an idea why TLS is fastest than TCP, i'd like to know .. :)
You will need a [client](#clients) to speak with sloki.
## 1.ii. Protocols
### 1.ii.a. Binary protocol (default)
The binary protocol has been made with performance in mind. Payloads looks like JSONRPC, but it's not.
```
REQUEST | RESPONSE
------------------------------------------- | --------------------------------------
{ | {
"m":"myMethod", | "r":true,
"p":["foo","bar"], | "id":"operation-uniq-id"
"id":"operation-uniq-id" | }
} |
```
* Payload is a little lighter compared to compliant JSONRPC protocol described below (i.e no `jsonrpc` version attribute, `method` become `m`, `params` become `p`, `result` become `r`)
* [Missive](https://github.com/StarryInternet/missive) package is used both server and client side to transform payloads into a binary format. Missive support zlib compression, but it's not used here and it's not recommended because of performance crumble. Missive is based on [fringe](https://github.com/StarryInternet/fringe), an extensible message framing over streams for nodejs.
### 1.ii.b. **JSONRPC**
The JSONRPC protocol has been chosen for interoperability.
```
REQUEST | RESPONSE
------------------------------------------- | --------------------------------------
{ | {
"jsonrpc":"2.0", | "jsonrpc":"2.0"
"method":"myMethod", | "result":true,
"params":["foo","bar"], | "id":"operation-uniq-id"
"id":"operation-uniq-id" | }
} |
```
* Raw and standard JSONRPC over TCP
* [jayson](https://github.com/tedeh/jayson) package is used server side. Actually only TCP transport is implemented, but HTTP(s) JSON API and websocket API may be implemented in the future.
-----
## Installation
## Server Installation
* ```npm install -g sloki```
## Usage
## Server Usage

@@ -61,2 +113,6 @@ * `sloki`

The client will load every methods that sloki server have, so, the client documentation is not really usefull
## Benchmarks

@@ -100,11 +156,14 @@

| Status | Command | Parameter | Description
| Status | Method | Parameter | Description
|:-----------------:|-------------------|---------------|----------------
| :heavy_check_mark:| quit | | disconnect (TCP/TLS clients only)
| :heavy_check_mark:| shutdown | | shutdown sloki
| :heavy_check_mark:| memory | | return sloki memory usage
| :heavy_check_mark:| clients | | return TCP/TLS connected clients
| :heavy_check_mark:| gc | | invoke gc(), for testing purpose
| :heavy_check_mark:| maxClients | | return TCP/TLS maxClients
| :heavy_check_mark:| maxClients | maxClients | set TCP/TLS maxClients
| :heavy_check_mark:| commands | | return available commands
| :heavy_check_mark:| memory | | return sloki memory usage
| :heavy_check_mark:| methods | | return sloki methods
| :heavy_check_mark:| quit | | disconnect (TCP/TLS clients only)
| :heavy_check_mark:| shutdown | | shutdown sloki
| :heavy_check_mark:| version | | return versions (sloki, lokijs, sloki-node-client)
| :heavy_check_mark:| wait | | wait for one second, for testing purpose

@@ -165,7 +224,7 @@ </p>

|:-----------------:|-------------------------------|-----------------------------------|----------------
| :heavy_check_mark:| find | collectionName, filter | find document(s)
| :heavy_check_mark:| get | collectionName, lokiId | return a document by his id
| :heavy_check_mark:| insert | collectionName, document | insert a document
| :heavy_check_mark:| insert | collectionName, document | insert one or more document(s)
| :heavy_check_mark:| remove | collectionName, document or id | remove one or more document(s)
| :heavy_check_mark:| update | collectionName, document | update a document
| :heavy_check_mark:| remove | collectionName, document or id | remove a document
| :heavy_check_mark:| find | collectionName, filter | find document(s)

@@ -221,2 +280,70 @@ </p>

TODO
`$ sloki --help`
```
=======================================================================
Sloki - a NodeJS Server for LokyJS
=======================================================================
Environnement variable Default
SLOKI_TCP_BINARY_ENABLE true
SLOKI_TCP_BINARY_PORT 6370
SLOKI_TCP_BINARY_HOST localhost
SLOKI_TCP_BINARY_MAX_CLIENTS 64
SLOKI_TLS_BINARY_ENABLE true
SLOKI_TLS_BINARY_PORT 6371
SLOKI_TLS_BINARY_HOST localhost
SLOKI_TLS_BINARY_MAX_CLIENTS 64
SLOKI_TCP_JSONRPC_ENABLE true
SLOKI_TCP_JSONRPC_PORT 6372
SLOKI_TCP_JSONRPC_HOST localhost
SLOKI_TCP_JSONRPC_MAX_CLIENTS 64
SLOKI_TLS_JSONRPC_ENABLE true
SLOKI_TLS_JSONRPC_PORT 6373
SLOKI_TLS_JSONRPC_HOST localhost
SLOKI_TLS_JSONRPC_MAX_CLIENTS 64
SLOKI_DIR /home/franck/.sloki
SLOKI_SHOW_OPS_INTERVAL 0
SLOKI_GC_INTERVAL 3600000
SLOKI_MEM_LIMIT 26094 Mb
-----------------------------------------------------------------------
Command Line Options Default
--tcp-binary-enable true
--tcp-binary-port 6370
--tcp-binary-host localhost
--tcp-binary-max-clients 64
--tls-binary-enable true
--tls-binary-port 6371
--tls-binary-host localhost
--tls-binary-max-clients 64
--tcp-jsonrpc-enable true
--tcp-jsonrpc-port 6372
--tcp-jsonrpc-host localhost
--tcp-jsonrpc-max-clients 64
--tls-jsonrpc-enable true
--tls-jsonrpc-port 6373
--tls-jsonrpc-host localhost
--tls-jsonrpc-max-clients 64
--dir /home/franck/.sloki
--show-ops-interval 0
--gc-interval 3600000
--mem-limit 26094 Mb
-----------------------------------------------------------------------
Examples:
$ sloki # will use defaults
$ sloki --tcp-binary-port=6370 --tcp-binary-host=localhost
```
The default values ​​can be overridden first with those of the environment variables,
and then those of the command line options.
* SLOKI_DIR `/path/to/sloki`
* default is user's home (~user/.sloki)
* subdirectory `dbs` contains lokijs databases
* subdirectory `certs` contains SSL Certificates for TLS
* directories will be created if not exist
* SLOKI_MEM_LIMIT
* by default, 80% of the available memory
* SLOKI_GC_INTERVAL
* run nodejs garbage collector at regular interval (value in milliseconds)
const argv = require('minimist')(process.argv.slice(2));
const use = require('abrequire');
const Client = use('src/Client');
const readline = require('readline');
const run = require('./run');
let Client;
if (process.env.NODE_ENV === 'dev') {
Client = require('../../../clients/node');
process.on('uncaughtException', function (err) {
console.log('uncaughtException');
console.log(err);
});
process.on('unhandledRejection', (reason, p) => {
console.log('unhandledRejection reason');
console.log(reason);
console.log('promise');
console.log(p);
console.log('stack');
console.log(reason.stack);
});
} else {
Client = require('sloki-node-client');
}
if (!argv._[0]) {

@@ -11,41 +32,15 @@ require('./usage');

const client = new Client(argv._[0]);
function onConnected() {
console.log('connected');
const completions = client.commandsName();
(async function() {
function autoComplete(line) {
const hits = completions.filter((c) => c.startsWith(line));
return [hits.length ? hits : completions, line];
}
const client = new Client(argv._[0]);
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout,
completer:autoComplete
});
rl.setPrompt('> ');
rl.prompt();
rl.on('line', (data) => {
if (data === 'quit') {
rl.close();
client.quit();
await client.connect((err) => {
if (err) {
console.log(err.message);
return;
}
rl.prompt();
run(argv._[0], client);
});
}
function onError(err) {
throw Error(err);
}
client.connect().then(onConnected);
client.on('close', () => {
console.log('close');
});
client.on('error', onError);
})();

@@ -1,8 +0,16 @@

const use = require('abrequire');
const ENV = use('src/env');
const config = require('../config');
console.log('Usage: sloki-cli <url>');
console.log();
console.log('Examples:');
console.log(' sloki-cli tcp://localhost:'+ENV.NET_TCP_PORT);
console.log(' sloki-cli tls://localhost:'+ENV.NET_TCP_PORT);
console.log('Examples with ports:');
console.log(`sloki-cli tcp://localhost:${config.TCP_BINARY_PORT}`);
console.log(`sloki-cli tls://localhost:${config.TLS_BINARY_PORT}`);
console.log(`sloki-cli binary://localhost:${config.TCP_BINARY_PORT}`);
console.log(`sloki-cli binarys://localhost:${config.TLS_BINARY_PORT}`);
console.log(`sloki-cli jsonrpc://localhost:${config.TCP_JSONRPC_PORT}`);
console.log(`sloki-cli jsonrpcs://localhost:${config.TLS_JSONRPC_PORT}`);
console.log('');
console.log('Notes:');
console.log(' * tcp protocol is an alias for binary protocol');
console.log(' * tls protocol is an alias for binarys protocol');
console.log(' * you may not specify the port, in which case the default ports will be used.');

@@ -7,3 +7,3 @@ const log = require('evillogger')({ ns:'loki' });

const ENV = require('./env');
const config = require('./config');
const shared = require('./methods/shared');

@@ -13,5 +13,5 @@

if (!fs.pathExistsSync(ENV.DATABASES_DIRECTORY)) {
fs.ensureDirSync(ENV.DATABASES_DIRECTORY);
log.info('Directory %s created', ENV.DATABASES_DIRECTORY);
if (!fs.pathExistsSync(config.SLOKI_DIR_DBS)) {
fs.ensureDirSync(config.SLOKI_DIR_DBS);
log.info(`directory ${config.SLOKI_DIR_DBS} created`);
}

@@ -22,9 +22,9 @@

for (file of klawSync(ENV.DATABASES_DIRECTORY)) {
for (file of klawSync(config.SLOKI_DIR_DBS)) {
dbName = path.basename(file.path).replace(/\.json/, '');
log.info(`Loading database ${file.path}`);
log.info(`loading database ${file.path}`);
shared.dbs[dbName] = new loki(file.path, shared.DEFAULT_DATABASE_OPTIONS);
}
const dbTestFile = path.resolve(ENV.DATABASES_DIRECTORY+'/test.json');
const dbTestFile = path.resolve(config.SLOKI_DIR_DBS+'/test.json');

@@ -31,0 +31,0 @@ if (!shared.dbs['test']) {

@@ -6,27 +6,20 @@ const log = require('evillogger')({ ns:'collection/find' });

const descriptor = {
name:'find',
categories:['collection'],
description:{
short:'Find a document'
},
parameters:[
{
name:'Collection name',
mandatory:true,
title:'find',
description:'Find a document',
type: 'object',
properties:{
'collection':{
alias:['col', 'c'],
description:'Collection name',
sanityCheck:{
type:'string',
reString:shared.RE_COLLETION_NAME,
reFlag:'i'
}
type:'string',
pattern:shared.RE_COLLETION_NAME,
patternFlag:'i'
},
{
name:'Filters',
mandatory:false,
'filters':{
alias:['f'],
description:'Filters',
sanityCheck:{
type:'object'
}
type:'object'
}
]
},
required:['collection']
};

@@ -45,6 +38,6 @@

*/
function handler(params, callback, socket) {
const databaseName = socket.loki.currentDatabase;
const collectionName = params[0];
const filters = params[1];
function handler(params, context, callback) {
const databaseName = context.session.loki.currentDatabase;
const collectionName = params.collection;
const filters = params.filters;

@@ -51,0 +44,0 @@ if (!shared.collectionExists(databaseName, collectionName, callback)) {

@@ -5,27 +5,19 @@ const shared = require('../../shared');

const descriptor = {
name:'get',
categories:['collection'],
description:{
short:'Get a document by id'
},
parameters:[
{
name:'Collection name',
mandatory:true,
title:'get',
description:'Get a document by id',
type: 'object',
properties:{
'collection':{
alias:['col', 'c'],
description:'Collection name',
sanityCheck:{
type:'string',
reString:shared.RE_COLLETION_NAME,
reFlag:'i'
}
type:'string',
pattern:shared.RE_COLLETION_NAME,
patternFlag:'i'
},
{
name:'Unique ID',
mandatory:true,
'id':{
description:'Loki id',
sanityCheck:{
type:'number'
}
type:'number'
}
]
},
required:['collection', 'id']
};

@@ -44,6 +36,6 @@

*/
function handler(params, callback, socket) {
const databaseName = socket.loki.currentDatabase;
const collectionName = params[0];
const lokiId = params[1];
function handler(params, context, callback) {
const databaseName = context.session.loki.currentDatabase;
const collectionName = params.collection;
const lokiId = params.id;

@@ -50,0 +42,0 @@ if (!shared.collectionExists(databaseName, collectionName, callback)) {

@@ -6,35 +6,25 @@ const log = require('evillogger')({ ns:'collection/insert' });

const descriptor = {
name:'insert',
categories:['collection'],
description:{
short:'Add a document'
},
parameters:[
{
name:'Collection name',
mandatory:true,
title:'insert',
description:'Insert a document',
type: 'object',
properties:{
'collection':{
alias:['col', 'c'],
description:'Collection name',
sanityCheck:{
type:'string',
reString:shared.RE_COLLETION_NAME,
reFlag:'i'
}
type:'string',
pattern:shared.RE_COLLETION_NAME,
patternFlag:'i'
},
{
name:'Document',
mandatory:true,
'document':{
alias:['doc', 'd'],
description:'Document',
sanityCheck:{
type:'object'
}
type:'object'
},
{
name:'Options',
mandatory:false,
'options':{
alias:['opts', 'o'],
description:'Insert options',
sanityCheck:{
type:'object'
}
type:'object'
}
]
},
required:['collection', 'document']
};

@@ -53,7 +43,7 @@

*/
function handler(params, callback, socket) {
const databaseName = socket.loki.currentDatabase;
const collectionName = params[0];
const doc = params[1];
const options = params[2];
function handler(params, context, callback) {
const databaseName = context.session.loki.currentDatabase;
const collectionName = params.collection;
const doc = params.document;
const options = params.options;

@@ -60,0 +50,0 @@ if (!shared.databaseSelected(databaseName, callback)) {

@@ -6,25 +6,29 @@ const log = require('evillogger')({ ns:'collection/remove' });

const descriptor = {
name:'remove',
categories:['collection'],
description:{
short:'Remove a document by id'
title:'remove',
description:'Remove a document by id',
type: 'object',
properties:{
'collection':{
alias:['col', 'c'],
description:'Collection name',
type:'string',
pattern:shared.RE_COLLETION_NAME,
patternFlag:'i'
},
'document':{
alias:['doc', 'd'],
description:'Document',
type:'object'
},
'id':{
description:'Document ID',
type:'number'
}
},
parameters:[
oneOf:[
{
name:'Collection name',
mandatory:true,
description:'Collection name',
sanityCheck:{
type:'string',
reString:shared.RE_COLLETION_NAME,
reFlag:'i'
}
required:['collection', 'document']
},
{
name:'document or document id',
mandatory:true,
description:'Document or document ID',
sanityCheck:{
type:['number', 'object']
}
required:['collection', 'id']
}

@@ -45,6 +49,6 @@ ]

*/
function handler(params, callback, socket) {
const databaseName = socket.loki.currentDatabase;
const collectionName = params[0];
const documentOrId = params[1];
function handler(params, context, callback) {
const databaseName = context.session.loki.currentDatabase;
const collectionName = params.collection;
const documentOrId = params.document || params.id;

@@ -51,0 +55,0 @@ if (!shared.collectionExists(databaseName, collectionName, callback)) {

@@ -6,27 +6,20 @@ const log = require('evillogger')({ ns:'collection/remove' });

const descriptor = {
name:'update',
categories:['collection'],
description:{
short:'Update a document'
},
parameters:[
{
name:'Collection name',
mandatory:true,
title:'update',
description:'Update a document',
type: 'object',
properties:{
'collection':{
alias:['col', 'c'],
description:'Collection name',
sanityCheck:{
type:'string',
reString:shared.RE_COLLETION_NAME,
reFlag:'i'
}
type:'string',
pattern:shared.RE_COLLETION_NAME,
patternFlag:'i'
},
{
name:'document',
mandatory:true,
'document':{
alias:['doc', 'd'],
description:'Document',
sanityCheck:{
type:['object']
}
type:'object'
}
]
},
required:['collection', 'document']
};

@@ -45,6 +38,6 @@

*/
function handler(params, callback, socket) {
const databaseName = socket.loki.currentDatabase;
const collectionName = params[0];
const doc = params[1];
function handler(params, context, callback) {
const databaseName = context.session.loki.currentDatabase;
const collectionName = params.collection;
const doc = params.document;

@@ -51,0 +44,0 @@ if (!shared.collectionExists(databaseName, collectionName, callback)) {

@@ -5,27 +5,19 @@ const shared = require('../../shared');

const descriptor = {
name:'addCollection',
categories:['database'],
description:{
short:'Add a collection into currently selected database'
},
parameters:[
{
name:'Collection name',
mandatory:true,
title:'addCollection',
description:'Add a collection into currently selected database',
type:'object',
properties:{
'collection':{
alias:['col', 'c'],
description:'Collection name',
sanityCheck:{
type:'string',
reString:shared.RE_COLLETION_NAME,
reFlag:'i'
}
type:'string',
pattern:shared.RE_COLLETION_NAME,
patternFlag:'i'
},
{
name:'Options',
mandatory:false,
'options':{
description:'Collection options',
sanityCheck:{
type:'object'
}
type:'object'
}
]
},
required:['collection']
};

@@ -44,6 +36,6 @@

*/
function handler(params, callback, socket) {
const databaseName = socket.loki.currentDatabase;
const collectionName = params[0];
const collectionOptions = params[1];
function handler(params, context, callback) {
const databaseName = context.session.loki.currentDatabase;
const collectionName = params.collection;
const collectionOptions = params.options;

@@ -50,0 +42,0 @@ if (!shared.databaseSelected(databaseName, callback)) {

const Method = require('../../Method');
const descriptor = {
name:'db',
categories:['database'],
description:{
short:'Return currently selected database name',
},
parameters:[]
title:'db',
description:'Return currently selected database name'
};

@@ -23,4 +19,4 @@

*/
function handler(params, callback, socket) {
callback(null, socket.loki.currentDatabase);
function handler(params, context, callback) {
callback(null, context.session.loki.currentDatabase);
}

@@ -27,0 +23,0 @@

@@ -5,19 +5,14 @@ const shared = require('../../shared');

const descriptor = {
name:'getCollection',
categories:['database'],
description:{
short:'Return collection properties',
},
parameters:[
{
name:'Collection name',
mandatory:true,
title:'getCollection',
description:'Return collection properties',
properties:{
'collection':{
alias:['col', 'c'],
description:'Collection name',
sanityCheck:{
type:'string',
reString:shared.RE_COLLETION_NAME,
reFlag:'i'
}
type:'string',
pattern:shared.RE_COLLETION_NAME,
patternFlag:'i'
}
]
},
required:['collection']
};

@@ -36,5 +31,5 @@

*/
function handler(params, callback, socket) {
const databaseName = socket.loki.currentDatabase;
const collectionName = params[0];
function handler(params, context, callback) {
const databaseName = context.session.loki.currentDatabase;
const collectionName = params.collection;

@@ -41,0 +36,0 @@ if (!shared.databaseSelected(databaseName, callback)) {

@@ -5,8 +5,4 @@ const shared = require('../../shared');

const descriptor = {
name:'listCollections',
categories:['database'],
description:{
short:'Return collections in currently selected database',
},
parameters:[]
title:'listCollections',
description:'Return collections in currently selected database'
};

@@ -25,4 +21,4 @@

*/
function handler(params, callback, socket) {
const databaseName = socket.loki.currentDatabase;
function handler(params, context, callback) {
const databaseName = context.session.loki.currentDatabase;

@@ -29,0 +25,0 @@ if (!shared.databaseSelected(databaseName, callback)) {

@@ -5,8 +5,4 @@ const shared = require('../../shared');

const descriptor = {
name:'listDatabases',
categories:['database'],
description:{
short:'Return available databases',
},
parameters:[]
title:'listDatabases',
description:'Return available databases'
};

@@ -25,3 +21,3 @@

*/
function handler(params, callback) {
function handler(params, context, callback) {

@@ -28,0 +24,0 @@ const dbs = [];

const log = require('evillogger')({ ns:'database/loadDatabase' });
const shared = require('../../shared');
const Method = require('../../Method');
const path = require('path');
const loki = require('lokijs');
const descriptor = {
name:'loadDatabase',
categories:['database'],
description:{
short:'Select a database (if not exist, a new db will be created)'
},
parameters:[
{
name:'database name',
mandatory:true,
mandatoryError:'Database name is mandatory',
title:'loadDatabase',
description:'Select a database (if not exist, a new db will be created)',
properties:{
'database':{
alias:['db', 'd'],
description:'Database name',
sanityCheck:{
type:'string',
reString:shared.RE_DATABASE_NAME,
reFlag:'i',
}
type:'string',
pattern:shared.RE_DATABASE_NAME,
patternFlag:'i'
},
{
name:'Options',
mandatory:false,
'options':{
alias:['opts', 'o'],
description:'Database options',
sanityCheck:{
type:'object'
}
type:'object'
}
]
},
required:['database']
};

@@ -47,30 +36,29 @@

*/
function handler(params, callback, socket) {
const databaseName = params[0];
const databaseOptions = params[1]||{};
function handler(params, context, callback) {
const databaseName = params.database;
const databaseOptions = params.options;
if (shared.dbs[databaseName]) {
socket.loki.currentDatabase = databaseName;
log.info(`${socket.id}: current database is now ${databaseName} (loaded)`);
callback(null, shared.dbs[databaseName]);
return;
function ret(result, created) {
context.session.loki.currentDatabase = databaseName;
if (created) {
log.info(`${context.session.id}: current database is now ${databaseName} (created)`);
} else {
log.info(`${context.session.id}: current database is now ${databaseName}`);
}
callback(null, result);
}
const dbPath = path.resolve(shared.ENV.DATABASES_DIRECTORY+`/${databaseName}.json`);
const options = Object.assign(shared.DEFAULT_DATABASE_OPTIONS, databaseOptions||{});
shared.getDatabase(databaseName, (err, result) => {
if (result) {
ret(result);
return;
}
options.autoloadCallback = () => {
socket.loki.currentDatabase = databaseName;
log.info(`${socket.id}: current database is now ${databaseName} (created)`);
callback(null, shared.dbs[databaseName]);
};
shared.createDatabase(databaseName, databaseOptions, (err, result) => {
ret(result, true);
});
});
//log.info(`${socket.id}: creating database ${databaseName}`);
shared.dbs[databaseName] = new loki(dbPath, options);
// by default, immediate save (force flush) after creating database
shared.ENV.DATABASES_FORCE_SAVE_ON_CREATE && shared.dbs[databaseName].save();
}
module.exports = new Method(descriptor, handler);

@@ -5,8 +5,4 @@ const shared = require('../../shared');

const descriptor = {
name:'saveDatabase',
categories:['database'],
description:{
short:'Force save of currently selected database',
},
parameters:[]
title:'saveDatabase',
description:'Force save of currently selected database'
};

@@ -26,4 +22,4 @@

*/
function handler(params, callback, socket) {
const databaseName = socket.loki.currentDatabase;
function handler(params, context, callback) {
const databaseName = context.session.loki.currentDatabase;

@@ -34,3 +30,3 @@ if (!shared.databaseSelected(databaseName, callback)) {

shared.dbs[databaseName].saveDatabase((err) => {
shared.dbs[databaseName].saveDatabase(err => {
if (err) {

@@ -37,0 +33,0 @@ callback({

const Method = require('../../Method');
const descriptor = {
name:'clients',
categories:['server'],
description:{
short:'Return connected clients list'
},
parameters:[]
title:'clients',
description:'Return connected clients list'
};

@@ -23,6 +19,6 @@

*/
function handler(params, callback, socket) {
callback(null, Object.keys(socket.server.clients));
function handler(params, context, callback) {
callback(null, Object.keys(context.server.clients));
}
module.exports = new Method(descriptor, handler);

@@ -1,37 +0,30 @@

const shared = require('../../shared');
const log = require('evillogger')({ ns:'server/maxClients' });
const Method = require('../../Method');
const descriptor = {
name:'maxClients',
categories:['server'],
description:{
short:'Return or set maximum number of allowed simultaneous connected clients (TCP/TLS)',
},
parameters:[
{
name:'value',
mandatory:false,
title:'maxClients',
description:'Return or set maximum number of allowed simultaneous connected clients (TCP/TLS)',
properties:{
'value':{
description:'Maximum number of allowed simultaneous connected clients (between 1 and 1000)',
sanityCheck:{
type:'number',
reString:'^([1-9][0-9]{0,2}|1000)$',
reFlag:'',
reError:'maxClients should be a number between 1 and 1000'
}
type:'number',
minimum: 1,
maximum: 1000,
}
]
}
};
function handler(params, callback) {
if (!params) {
callback(null, shared.ENV.NET_TCP_MAX_CLIENTS);
function handler(params, context, callback) {
if (!params.value) {
callback(null, context.server.getMaxClients());
return;
}
const maxClient = params[0];
const maxClients = params.value;
shared.ENV.NET_TCP_MAX_CLIENTS = parseInt(maxClient);
callback(null, shared.ENV.NET_TCP_MAX_CLIENTS);
context.server.setMaxClients(maxClients);
log.info(`${context.session.id}: maxClients has been set to ${maxClients} for protocol ${context.server.protocol}`);
callback(null, maxClients);
}
module.exports = new Method(descriptor, handler);
const Method = require('../../Method');
const prettyBytes = require('pretty-bytes');
const os = require('os');
const descriptor = {
name:'memory',
categories:['server'],
description:{
short:'Return sloki process memory usage',
},
parameters:[]
title:'memory',
description:'Return sloki process memory usage'
};

@@ -24,3 +21,3 @@

*/
function handler(params, callback) {
function handler(params, context, callback) {
const used = process.memoryUsage();

@@ -31,5 +28,14 @@ for (const key in used) {

callback(null, used);
const osmem = {
free:os.freemem(),
total:os.totalmem()
};
for (const key in osmem) {
osmem[key] = prettyBytes(osmem[key]);
}
callback(null, { process:used, os:osmem });
}
module.exports = new Method(descriptor, handler);
const Method = require('../../Method');
const descriptor = {
name:'methods',
categories:['server'],
description:{
short:'Return methods list',
},
parameters:[]
title:'methods',
description:'Return methods list'
};

@@ -24,3 +20,3 @@

*/
function handler(params, callback) {
function handler(params, context, callback) {
callback(null, require('../../').listWithDescriptor());

@@ -27,0 +23,0 @@ }

const Method = require('../../Method');
const descriptor = {
name:'quit',
categories:['server'],
description:{
short:'Disconnect',
},
parameters:[]
title:'quit',
description:'Disconnect'
};

@@ -24,7 +19,7 @@

*/
function handler(params, callback, socket) {
function handler(params, context, callback) {
callback(null, 'bye');
socket.end();
context.session.end();
}
module.exports = new Method(descriptor, handler);

@@ -5,8 +5,4 @@ const Method = require('../../Method');

const descriptor = {
name:'shutdown',
categories:['server'],
description:{
short:'Shutdown sloki server',
},
parameters:[]
title:'shutdown',
description:'Shutdown sloki server'
};

@@ -24,3 +20,3 @@

*/
function handler(params, callback) {
function handler(params, context, callback) {
require('../../../server').stop();

@@ -27,0 +23,0 @@ callback();

const Method = require('../../Method');
const descriptor = {
name:'wait',
categories:['server'],
description:{
short:'Wait for one sec'
},
parameters:[]
title:'wait',
description:'Wait for one sec'
};

@@ -22,3 +18,3 @@

*/
function handler(params, callback) {
function handler(params, context, callback) {
setTimeout(() => {

@@ -25,0 +21,0 @@ callback();

@@ -5,4 +5,4 @@ const log = require('evillogger')({ ns:'methods' });

const commands = {};
const commandsDescriptor = {};
const methods = {};
const methodsDescriptor = {};
const reDirname = new RegExp(__dirname+'/');

@@ -26,17 +26,16 @@ const showLog = !process.mainModule.filename.match(/\/cli/);

}
showLog && log.debug(`Command registered ${cmdBase}/${cmdName}`);
commands[cmdName] = require(file.path);
commandsDescriptor[cmdName] = commands[cmdName].getDescriptor();
showLog && log.debug(`method registered ${cmdBase}/${cmdName}`);
methods[cmdName] = require(file.path);
methodsDescriptor[cmdName] = methods[cmdName].getDescriptor();
}
function getHandler(command, params, scope) {
if (!commands[command]) {
function getHandler(method, params, context) {
if (!methods[method]) {
return;
}
// @TODO: optimize scope ?
if (scope) {
return commands[command].handle.bind(scope);
if (context) {
return methods[method].handle.bind(context);
} else {
return commands[command].handle;
return methods[method].handle;
}

@@ -46,12 +45,12 @@ }

function list() {
return commands;
return methods;
}
function listWithDescriptor() {
return commandsDescriptor;
return methodsDescriptor;
}
function exists(command) {
if (commands[command]) {
function exists(method) {
if (methods[method]) {
return true;

@@ -62,2 +61,10 @@ }

function exec(method, params, context, callback) {
try {
methods[method].handle(params, context, callback);
} catch(err) {
callback(err);
}
}
module.exports = {

@@ -67,3 +74,4 @@ list,

exists,
getHandler
getHandler,
exec
};

@@ -1,93 +0,193 @@

const log = require('evillogger')({ ns:'Command' });
const log = require('evillogger')({ ns:'Method' });
const shared = require('./shared');
const config = require('../config');
// http://jsonrpc.org/spec.html#error_object
const ERROR_CODE_PARAMETER = -32602;
function triggerError(msg, callback) {
callback({ code:ERROR_CODE_PARAMETER, message:msg });
log.warn(msg);
callback({ code:shared.ERROR_CODE_PARAMETER, message:msg });
if (config.TCP_ENGINE!='binary') {
log.warn(msg);
}
}
function triggerErrorInternal(msg, callback) {
callback({ code:shared.ERROR_CODE_INTERNAL, message:msg });
if (config.TCP_ENGINE!='binary') {
log.warn(msg);
}
}
function Command(descriptor, handler) {
let mandatoryParametersCount = 0;
let descriptorPropertiesCount = 0;
if (descriptor.properties) {
descriptorPropertiesCount = Object.keys(descriptor.properties).length;
}
function handle(params, callback) {
if (!descriptor.required) {
descriptor.required = [];
}
if (!descriptor.oneOf) {
descriptor.oneOf = [];
}
//
// Sanity Checks
//
function unwantedProperties(params) {
if (
params
&& Object.keys(params).length>0
&& (!descriptor.properties || !Object.keys(descriptor.properties).length)
) {
return true;
}
return false;
}
if (!params || !params.length) {
if (mandatoryParametersCount) {
triggerError(`${descriptor.name}: number of parameters should be at least ${mandatoryParametersCount}`, callback);
function handle(params, context, callback) {
if (!callback) {
callback = context;
context = this;
}
if (!params) params = {};
if (descriptor.title.match(/insert|update/)) {
if (config.MEM_LIMIT_REACHED) {
triggerErrorInternal(`method "${descriptor.title}": memory limit reached`, callback);
return;
}
}
// "this" is the socket socket if client is TCP/TLS
handler(params, callback, this);
// request has parameters, but the descriptor don't have
if (unwantedProperties(params)) {
triggerError(`method "${descriptor.title}" does NOT wait for any property`, callback);
return;
}
if (params.length<mandatoryParametersCount) {
triggerError(`${descriptor.name}: number of parameters should be at least ${mandatoryParametersCount}`, callback);
const paramsCount = Object.keys(params).length;
// request has no parameters, as specified in the descriptor
if (descriptorPropertiesCount === 0 && paramsCount === 0) {
handler(params, context, callback);
return;
}
// request has more parameters than expected in the descriptor
if (paramsCount>descriptorPropertiesCount) {
triggerError(`method "${descriptor.title}": too many properties, expected ${descriptorPropertiesCount}, find ${paramsCount}`, callback);
return;
}
if (params.length>descriptor.parameters.length) {
if (descriptor.parameters.length>0) {
triggerError(`${descriptor.name}: number of parameters should be lower or equal than ${descriptor.parameters.length}`, callback);
return;
let property;
for (const prop in descriptor.properties) {
property = descriptor.properties[prop];
if (property.alias && property.alias.length) {
for (const alias of property.alias) {
if (params[alias] != undefined) {
params[prop] = params[alias];
delete params[alias];
}
}
}
triggerError(`${descriptor.name}: this method does not wait for parameters`, callback);
}
let parameter;
for (const prop in descriptor.properties) {
for (let i = 0; i< descriptor.parameters.length; i++) {
property = descriptor.properties[prop];
parameter = descriptor.parameters[i];
// a mandatory property is missing
if (descriptor.required.indexOf(prop)>=0 && params[prop] === undefined) {
if (property.alias) {
triggerError(`method "${descriptor.title}": property ${prop} (alias ${property.alias}) is mandatory`, callback);
} else {
triggerError(`method "${descriptor.title}": property ${prop} is mandatory`, callback);
}
return;
}
if (!parameter.sanityCheck) {
// non mandatory property
if (params[prop] === undefined) {
continue;
}
if (
params[i] != null && params[i] != undefined &&
(
(typeof parameter.sanityCheck.type === 'object' && parameter.sanityCheck.type.indexOf(typeof params[i])<0) ||
(typeof parameter.sanityCheck.type === 'string' && typeof params[i] != parameter.sanityCheck.type)
)
) {
triggerError(`${descriptor.name}: wrong type for parameter '${parameter.name}' : found '${typeof params[i]}', expected '${parameter.sanityCheck.type}'`, callback);
// check type
if (property.type != typeof params[prop]) {
triggerError(`method "${descriptor.title}": property "${prop}" should be a ${property.type}, found ${typeof params[prop]}`, callback);
return;
}
if (params[i] === null || params[i] === undefined && parameter.mandatory) {
triggerError(`${descriptor.name}: parameter '${parameter.name}' is mandatory`, callback);
return;
}
if (parameter.sanityCheck.re) {
// re is a compiled regexp (see _compileDescriptorParametersRegexp)
if (!parameter.sanityCheck.re.test(params[i])) {
if (parameter.sanityCheck.reError) {
triggerError(`${descriptor.name}: ${parameter.sanityCheck.reError}`, callback);
if (property.type === 'string') {
if (property.pattern) {
// patternRe is the compiled version of the regexp, see _compileDescriptorParametersRegexp
if (!property.patternRe.test(params[prop])) {
triggerError(`method "${descriptor.title}": property "${prop}" does not match regular expression ${property.pattern}`, callback);
return;
}
}
}
triggerError(`${descriptor.name}: parameter '${parameter.name}' does not match regular expression ${parameter.sanityCheck.reString}`, callback);
if (property.type === 'number') {
// greater than
if (typeof property.minimum === 'number' && params[prop]<property.minimum) {
triggerError(`method "${descriptor.title}": property "${prop}" should be > ${property.minimum}`, callback);
return;
}
// lower than
if (typeof property.maximum === 'number' && params[prop]>property.maximum) {
triggerError(`method "${descriptor.title}": property "${prop}" should be < ${property.maximum}`, callback);
return;
}
}
}
if (descriptor.oneOf.length) {
let oneOfMatch = false;
let multipleOneOfFound = false;
let oneOfStr = [];
let strs = [];
for (const oneof of descriptor.oneOf) {
let found = 0;
for (const requiredProperty of oneof.required) {
strs.push(requiredProperty);
if (params[requiredProperty] != undefined) {
found++;
}
}
if (found === descriptor.oneOf.length) {
if (!oneOfMatch) {
oneOfMatch = true;
} else {
multipleOneOfFound = true;
}
}
oneOfStr.push(strs.join(', '));
strs = [];
}
oneOfStr = oneOfStr.join('" OR "');
if (multipleOneOfFound) {
triggerError(`method "${descriptor.title}": mandatory properties conflict, please specify properties "${oneOfStr}"`, callback);
return;
}
if (!oneOfMatch) {
triggerError(`method "${descriptor.title}": mandatory properties are missing (at least "${oneOfStr}")`, callback);
return;
}
}
//
// Sanity Check passed successfully
//
// "this" is the socket socket if client is TCP/TLS
handler(params, callback, this);
if (params.nr) {
// if "nr" (like "no response") passed in params, override callback with an empty one
callback = () => {};
}
handler(params, context, callback);
}

@@ -99,40 +199,15 @@

function _hasParametersInDescriptor() {
if (
!descriptor
||
!descriptor.parameters
||
!descriptor.parameters.length
) {
return false;
}
return true;
}
function _compileDescriptorParametersRegexp() {
if (!_hasParametersInDescriptor()) {
if (!descriptorPropertiesCount) {
return;
}
let parameter;
for (let i = 0; i< descriptor.parameters.length; i++) {
parameter = descriptor.parameters[i];
if (parameter.mandatory) {
mandatoryParametersCount+=1;
for (const prop in descriptor.properties) {
if (descriptor.properties[prop].pattern) {
descriptor.properties[prop].patternRe = new RegExp(
descriptor.properties[prop].pattern,
descriptor.properties[prop].patternFlag||''
);
}
if (!parameter.sanityCheck) {
continue;
}
parameter.sanityCheck.re = new RegExp(
parameter.sanityCheck.reString,
parameter.sanityCheck.reFlag
);
}

@@ -139,0 +214,0 @@

const log = require('evillogger')({ ns:'methods/shared' });
const ENV = require('../env');
const path = require('path');
const loki = require('lokijs');
const config = require('../config');
const dbs = {};
const collections = {};
// http://jsonrpc.org/spec.html#error_object
const ERROR_CODE_PARAMETER = -32602;

@@ -14,3 +18,3 @@ const ERROR_CODE_INTERNAL = -32603;

autosave:true,
autosaveInterval:ENV.DATABASES_AUTOSAVE_INTERVAL
autosaveInterval:config.DATABASES_AUTOSAVE_INTERVAL
};

@@ -57,3 +61,46 @@

function getDatabase(databaseName, callback) {
if (!dbs[databaseName]) {
callback(null, undefined);
return;
}
// avoid circular structure: ignore autosaveHandle, persistenceAdapter
callback(null, {
filename:dbs[databaseName].filename,
databaseVersion:dbs[databaseName].databaseVersion,
engineVersion:dbs[databaseName].engineVersion,
autosave:dbs[databaseName].autosave,
autosaveInterval:dbs[databaseName].autosaveInterval,
throttledSaves:dbs[databaseName].throttledSaves,
options:dbs[databaseName].options,
persistenceMethod:dbs[databaseName].persistenceMethod,
persistenceAdapter:typeof dbs[databaseName].persistenceAdapter,
throttledSavePending:dbs[databaseName].throttledSavePending,
verbose:dbs[databaseName].verbose,
ENV:dbs[databaseName].ENV,
name:dbs[databaseName].name
});
}
function createDatabase(databaseName, databaseOptions, callback) {
const dbPath = path.resolve(config.SLOKI_DIR_DBS+`/${databaseName}.json`);
const options = Object.assign(DEFAULT_DATABASE_OPTIONS, databaseOptions||{});
options.autoloadCallback = () => {
getDatabase(databaseName, (err, result) => {
callback(null, result);
});
};
dbs[databaseName] = new loki(dbPath, options);
// by default, immediate save (force flush) after creating database
config.DATABASES_FORCE_SAVE_ON_CREATE && dbs[databaseName].save();
}
module.exports = {
getDatabase,
createDatabase,
dbs,

@@ -67,4 +114,3 @@ collections,

ERROR_CODE_PARAMETER,
ERROR_CODE_INTERNAL,
ENV
ERROR_CODE_INTERNAL
};
const log = require('evillogger')({ ns:'server' });
const use = require('abrequire');
const tcp = require('./transports/tcpJayson');
const path = require('path');
const loki = require('./loki');
const ENV = use('src/env');
const binaryServer = require('./protocols/binary');
const jsonRpcServer = require('./protocols/jsonrpc');
const prettyBytes = require('pretty-bytes');
const async = require('async');
const ssl = require('./ssl');
let config = require('./config');
let closing = false;
let running = false;
let timerMemoryAlert;
let tcpBinaryServerInstance;
let tlsBinaryServerInstance;
let tcpJsonRpcServerInstance;
let tlsJsonRpcServerInstance;
const memoryAlertInterval = 1000;
function memLimitBytes() {
return config.MEM_LIMIT*1024*1024;
}
function memLimitBytesHuman() {
return prettyBytes(memLimitBytes());
}
function handleSignals() {

@@ -28,28 +45,106 @@ process.on('SIGINT', handleSignalSIGINT);

function start(options, callback) {
function start(callback) {
if (typeof options === 'function') {
callback = options;
options = null;
}
loki.initialize();
// already running !
if (running) {
callback && callback('ERUNNING');
if (callback) {
callback('ERUNNING');
}
return;
}
log.info(
'server starting ... (%s:%s)',
ENV.NET_TCP_HOST,
ENV.NET_TCP_PORT
);
tcp.start((err) => {
if (err) {
log.error(err);
return;
config = Object.assign(config, options||{});
config.SLOKI_DIR_DBS = path.resolve(config.SLOKI_DIR+'/dbs');
async.series([
(next) => {
ssl.check(next);
},
(next) => {
if (!config.TCP_BINARY_ENABLE) {
next();
return;
}
tcpBinaryServerInstance = new binaryServer({
HOST:config.TCP_BINARY_HOST,
PORT:config.TCP_BINARY_PORT,
MAX_CLIENTS:config.TCP_BINARY_MAX_CLIENTS,
SSL:false,
SHOW_OPS_INTERVAL:config.SHOW_OPS_INTERVAL
});
tcpBinaryServerInstance.start(next);
},
(next) => {
if (!config.TLS_BINARY_ENABLE) {
next();
return;
}
tlsBinaryServerInstance = new binaryServer({
HOST:config.TLS_BINARY_HOST,
PORT:config.TLS_BINARY_PORT,
MAX_CLIENTS:config.TLS_BINARY_MAX_CLIENTS,
SSL:true,
SSL_PRIVATE_KEY:config.SSL_PRIVATE_KEY,
SSL_CERTIFICATE:config.SSL_CERTIFICATE,
SSL_CA:config.SSL_CA,
SHOW_OPS_INTERVAL:config.SHOW_OPS_INTERVAL
});
tlsBinaryServerInstance.start(next);
},
(next) => {
if (!config.TCP_JSONRPC_ENABLE) {
next();
return;
}
tcpJsonRpcServerInstance = new jsonRpcServer({
HOST:config.TCP_JSONRPC_HOST,
PORT:config.TCP_JSONRPC_PORT,
MAX_CLIENTS:config.TCP_JSONRPC_MAX_CLIENTS,
SSL:false,
SHOW_OPS_INTERVAL:config.SHOW_OPS_INTERVAL
});
tcpJsonRpcServerInstance.start(next);
},
(next) => {
if (!config.TLS_JSONRPC_ENABLE) {
next();
return;
}
tlsJsonRpcServerInstance = new jsonRpcServer({
HOST:config.TLS_JSONRPC_HOST,
PORT:config.TLS_JSONRPC_PORT,
MAX_CLIENTS:config.TLS_JSONRPC_MAX_CLIENTS,
SSL:true,
SSL_PRIVATE_KEY:config.SSL_PRIVATE_KEY,
SSL_CERTIFICATE:config.SSL_CERTIFICATE,
SSL_CA:config.SSL_CA,
SHOW_OPS_INTERVAL:config.SHOW_OPS_INTERVAL
});
tlsJsonRpcServerInstance.start(next);
}
running = true;
handleSignals();
callback && callback();
], (err) => {
if (!err) {
running = true;
handleSignals();
loki.initialize();
timerMemoryAlert = setInterval(memoryAlert, memoryAlertInterval);
if (callback) {
callback();
}
}
});
}

@@ -59,3 +154,5 @@

if (!running) {
callback && callback('ENOTRUNNING');
if (callback) {
callback('ENOTRUNNING');
}
return;

@@ -66,13 +163,55 @@ }

tcp.stop((err) => {
log.info('server stopped, exiting');
async.series([
tcpBinaryServerInstance.stop,
tlsBinaryServerInstance.stop,
tcpJsonRpcServerInstance.stop,
tlsJsonRpcServerInstance.stop
], (err) => {
log.warn('bye');
running = false;
clearInterval(timerMemoryAlert);
if (callback) {
callback(err);
return;
}
process.exit(err ? 1 : 0);
process.exit();
});
}
function dumpMemory(level, prefix, mem) {
log[level](
'%s rss=%s, allowed %s',
prefix,
prettyBytes(mem.rss),
memLimitBytesHuman(),
);
}
function memoryAlert() {
const mem = process.memoryUsage();
if (mem.rss>memLimitBytes()) {
if (!config.MEM_LIMIT_REACHED) {
dumpMemory('warn', 'memory: limit reached', mem);
config.MEM_LIMIT_REACHED = true;
} else {
dumpMemory('warn', 'memory: always above limit', mem);
}
} else {
if (config.MEM_LIMIT_REACHED) {
dumpMemory('warn', 'memory: back under limit', mem);
}
config.MEM_LIMIT_REACHED = false;
}
}
if (global.gc) {
log.info(`Garbage collector will run every ${config.GC_INTERVAL} ms`);
setInterval(() => {
global.gc();
}, config.GC_INTERVAL);
}
dumpMemory('info', 'memory:', process.memoryUsage());
module.exports = {

@@ -79,0 +218,0 @@ start,

@@ -1,6 +0,6 @@

const use = require('abrequire');
const ENV = use('src/env');
module.exports = {
tcp:'tcp://'+ENV.NET_TCP_HOST+':'+ENV.NET_TCP_PORT
binary:'binary://localhost:6370',
binarys:'binarys://localhost:6371',
jsonrpc:'jsonrpc://localhost:6372',
jsonrpcs:'jsonrpcs://localhost:6373'
};

@@ -8,6 +8,6 @@ const log = require('evillogger')({ ns:'tests' });

const fs = require('fs-extra');
const ENV = require('../src/env');
const config = require('../src/config');
const homedir = require('os').homedir();
const tests = {};
const testFailed = false;

@@ -35,3 +35,3 @@ let testName;

function cleanTestDatabases() {
if (!fs.pathExistsSync(ENV.DATABASES_DIRECTORY)) {
if (!fs.pathExistsSync(config.SLOKI_DIR_DBS)) {
return;

@@ -41,3 +41,3 @@ }

let file;
for (file of klawSync(ENV.DATABASES_DIRECTORY, { depthLimit:0 })) {
for (file of klawSync(config.SLOKI_DIR_DBS, { depthLimit:0 })) {
if (path.basename(file.path).match(/\_\_/)) {

@@ -50,9 +50,4 @@ console.log('removing', file.path);

function endTests() {
cleanTestDatabases();
if (testFailed) process.exit(-1);
process.exit(0);
}
function runTests() {
function runTests(engine, done) {
prepareTests();

@@ -69,3 +64,3 @@

const optionTap = [
'node_modules/tap/bin/run.js',
path.resolve('./node_modules/tap/bin/run.js'),
'--reporter='+reporter

@@ -75,3 +70,3 @@ ];

const optionTape = [
'node_modules/tape/bin/tape'
path.resolve('./node_modules/tape/bin/tape')
];

@@ -84,12 +79,20 @@

(test, next) => {
let options;
let args;
if (tester === 'tape') {
options = JSON.parse(JSON.stringify(optionTape));
args = JSON.parse(JSON.stringify(optionTape));
} else {
options = JSON.parse(JSON.stringify(optionTap));
args = JSON.parse(JSON.stringify(optionTap));
}
options.push(test);
args.push(test);
const s = spawn('node', options, { stdio:'inherit' });
process.env.SLOKI_SERVER_ENGINE = engine;
process.env.NODE_ENV = 'dev';
const opts = {
stdio:'inherit',
env:process.env
};
const s = spawn('node', args, opts);
s.on('close', (code) => {

@@ -101,23 +104,45 @@ if (code != 0) {

});
s.on('error', (err) => {
console.log(err);
process.exit(255);
});
},
() => {
if (process.env.CI) {
server.stop(endTests);
} else {
endTests();
}
}
done
);
}
if (process.env.CI) {
server.start((err) => {
if (err) {
throw new Error(err);
const options = {
SLOKI_DIR:path.resolve(homedir+'/.slokitest'),
MEM_LIMIT:62 // in Mb
};
server.start(options, (err) => {
if (err) {
throw new Error(err);
}
async.series([
(next) => {
cleanTestDatabases();
runTests('binary', next);
},
(next) => {
cleanTestDatabases();
runTests('binarys', next);
},
(next) => {
cleanTestDatabases();
runTests('jsonrpc', next);
},
(next) => {
cleanTestDatabases();
runTests('jsonrpcs', next);
},
(next) => {
server.stop(next);
}
setTimeout(runTests, 1000);
], () => {
process.exit();
});
} else {
cleanTestDatabases();
runTests();
}
});

@@ -1,4 +0,5 @@

require('./client')(__filename, (test, client) => {
require('./client')(__filename, (test, client, end) => {
client.close();
test.end();
end();
});

@@ -1,7 +0,8 @@

require('./client')(__filename, (test, client) => {
require('./client')(__filename, (test, client, end) => {
try {
client.quit((err) => {
client.quit(err => {
test.deepEqual(err, undefined, 'method should not return an error');
test.pass('should be disconnected by the server');
test.end();
end();
});

@@ -11,3 +12,4 @@ } catch(e) {

test.end();
end();
}
});

@@ -1,13 +0,13 @@

const use = require('abrequire');
const Client = require('sloki-node-client');
const endpoint = require('../endpoints').tcp;
const ENV = use('src/env');
const Client = require('../../../clients/node');
const config = require('../../src/config');
const engine = process.env.SLOKI_SERVER_ENGINE||'tcpbinary';
const endpoint = require('../endpoints')[engine];
let maxClients = ENV.NET_TCP_MAX_CLIENTS;
const MAX_CLIENTS = config.getMaxClients(engine);
require('./client')(__filename, (test, client) => {
test.test('client1: getMaxClients', (subtest) => {
require('./client')(__filename, (test, client, end) => {
test.test('client1: getMaxClients', subtest => {
client.maxClients((err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');
subtest.equal(result, maxClients, 'client1: maxClients should return '+maxClients);
subtest.equal(result, MAX_CLIENTS, 'client1: maxClients should return '+MAX_CLIENTS);
subtest.end();

@@ -17,7 +17,14 @@ });

test.test('client1: setMaxClients', (subtest) => {
maxClients=1;
client.maxClients(maxClients, (err, result) => {
test.test('client1: setMaxClients', subtest => {
client.maxClients({ value:'a' }, (err) => {
const expectedError = { code: -32602, message: 'method "maxClients": property "value" should be a number, found string' };
subtest.deepEqual(err, expectedError, 'method should an return error');
subtest.end();
});
});
test.test('client1: setMaxClients', subtest => {
client.maxClients({ value:1 }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');
subtest.equal(result, maxClients, 'client1: maxClients should be set to '+maxClients);
subtest.equal(result, 1, 'client1: maxClients should be set to 1');
subtest.end();

@@ -27,6 +34,6 @@ });

test.test('client1: getMaxClients', (subtest) => {
test.test('client1: getMaxClients', subtest => {
client.maxClients((err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');
subtest.equal(result, maxClients, 'client1: maxClients should be '+maxClients);
subtest.equal(result, 1, 'client1: maxClients should be 1');
subtest.end();

@@ -36,7 +43,8 @@ });

test.test('client2: hit maxClients', (subtest) => {
const client2 = new Client(endpoint);
test.test('client2: hit maxClients', subtest => {
const client2 = new Client(endpoint, { engine });
client2
.connect()
.then(() => {
subtest.notOk('connect succeed, should not');
})

@@ -46,11 +54,13 @@ .catch((err) => {

subtest.deepEqual(err, expectedErr, 'client2: should return '+JSON.stringify(expectedErr));
setTimeout(subtest.end, 200);
client2.close();
setTimeout(subtest.end, 500);
});
});
test.test('client1: restore maxClients', (subtest) => {
client.maxClients(ENV.NET_TCP_MAX_CLIENTS, (err, result) => {
test.test('client1: restore maxClients', subtest => {
client.maxClients({ value:MAX_CLIENTS }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');
subtest.equal(result, ENV.NET_TCP_MAX_CLIENTS, 'client1: maxClients should be set to '+ENV.NET_TCP_MAX_CLIENTS);
subtest.equal(result, MAX_CLIENTS, 'client1: maxClients should be set to '+MAX_CLIENTS);
subtest.end();
end();
});

@@ -57,0 +67,0 @@ });

@@ -1,3 +0,3 @@

require('./client')(__filename, (test, client) => {
test.test('db', (subtest) => {
require('./client')(__filename, (test, client, end) => {
test.test('db', subtest => {
client.db((err, result) => {

@@ -7,4 +7,5 @@ subtest.deepEqual(err, undefined, 'method should not return an error');

subtest.end();
end();
});
});
});
const dbName = '__testUse';
require('./client')(__filename, (test, client) => {
test.test('loadDatabase', (subtest) => {
client.loadDatabase(dbName, (err, result) => {
require('./client')(__filename, (test, client, end) => {
test.test('loadDatabase', subtest => {
// 'database' property got 2 aliases: 'db' and 'd', let's use 'db'
client.loadDatabase({ db:dbName }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');
subtest.ok(typeof result, 'object', 'should return database properties');
subtest.deepEqual(typeof result, 'object', 'should return database properties');
subtest.end();

@@ -12,3 +13,3 @@ });

test.test('db', (subtest) => {
test.test('db', subtest => {
client.db((err, result) => {

@@ -18,4 +19,5 @@ subtest.deepEqual(err, undefined, 'method should not return an error');

subtest.end();
end();
});
});
});
const dbName = '__testUseWithOptions_'+Date.now();
require('./client')(__filename, (test, client) => {
test.test('loadDatabase', (subtest) => {
client.loadDatabase(dbName, { autosave:false }, (err, result) => {
require('./client')(__filename, (test, client, end) => {
test.test('loadDatabase', subtest => {
client.loadDatabase({ database:dbName, options:{ autosave:false } }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');

@@ -10,2 +10,3 @@ subtest.deepEqual(typeof result, 'object', 'should return database properties');

subtest.end();
end();
});

@@ -12,0 +13,0 @@ });

@@ -1,3 +0,3 @@

require('./client')(__filename, (test, client) => {
test.test('listDatabases', (subtest) => {
require('./client')(__filename, (test, client, end) => {
test.test('listDatabases', subtest => {
client.listDatabases((err, result) => {

@@ -7,4 +7,5 @@ subtest.deepEqual(typeof result, 'object', 'should return an array');

subtest.end();
end();
});
});
});

@@ -53,10 +53,10 @@ const dbName = '__testAddCollection';

require('./client')(__filename, (test, client) => {
client.loadDatabase(dbName, (err, result) => {
require('./client')(__filename, (test, client, end) => {
client.loadDatabase({ database:dbName }, (err, result) => {
test.deepEqual(err, undefined, 'loadDatabase should not return any error');
test.deepEqual(typeof result, 'object', 'database loaded');
test.test('addCollection should failed if Collection name is null', (subtest) => {
test.test('addCollection should failed if Collection name is null', subtest => {
client.addCollection(null, (err, result) => {
const expected = { code: ERROR_CODE_PARAMETER, message: 'addCollection: parameter \'Collection name\' is mandatory' };
const expected = { code: ERROR_CODE_PARAMETER, message: 'method "addCollection": property collection (alias col,c) is mandatory' };
subtest.deepEqual(err, expected, `should return error ${expected.message}`);

@@ -68,5 +68,5 @@ subtest.deepEqual(result, undefined, 'result should be undefined');

test.test('addCollection should failed if Collection name is undefined', (subtest) => {
test.test('addCollection should failed if Collection name is undefined', subtest => {
client.addCollection(undefined, (err, result) => {
const expected = { code: ERROR_CODE_PARAMETER, message: 'addCollection: parameter \'Collection name\' is mandatory' };
const expected = { code: ERROR_CODE_PARAMETER, message: 'method "addCollection": property collection (alias col,c) is mandatory' };
subtest.deepEqual(err, expected, `should return error ${expected.message}`);

@@ -78,7 +78,8 @@ subtest.deepEqual(result, undefined, 'result should be undefined');

test.test(`addCollection should create collection '${collectionName}'`, (subtest) => {
client.addCollection(collectionName, (err, result) => {
test.test(`addCollection should create collection '${collectionName}'`, subtest => {
client.addCollection({ collection:collectionName }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');
subtest.deepEqual(result, expected, 'should return collection properties');
subtest.end();
end();
});

@@ -85,0 +86,0 @@ });

@@ -5,5 +5,5 @@ const dbName = '__testAddCollectionWithOptions';

require('./client')(__filename, (test, client) => {
test.test('loadDatabase', (subtest) => {
client.loadDatabase(dbName, (err, result) => {
require('./client')(__filename, (test, client, end) => {
test.test('loadDatabase', subtest => {
client.loadDatabase({ database:dbName }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');

@@ -15,7 +15,8 @@ subtest.ok(typeof result, 'object', 'should return database properties');

test.test('getCollection', (subtest) => {
client.getCollection(collectionName, (err, result) => {
test.test('getCollection', subtest => {
client.getCollection({ collection: collectionName }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');
subtest.deepEqual(result, null, 'should return '+collectionName+' properties');
subtest.end();
end();
});

@@ -22,0 +23,0 @@ });

@@ -58,4 +58,4 @@ const dbName = '__testAddCollectionWithOptions';

require('./client')(__filename, (test, client) => {
client.loadDatabase(dbName, (err, result) => {
require('./client')(__filename, (test, client, end) => {
client.loadDatabase({ database: dbName }, (err, result) => {

@@ -65,4 +65,4 @@ test.deepEqual(err, undefined, 'loadDatabase should not return any error');

test.test('addCollection', (subtest) => {
client.addCollection(collectionName, collectionOptions, (err, result) => {
test.test('addCollection', subtest => {
client.addCollection({ collection:collectionName, options: collectionOptions }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');

@@ -75,3 +75,3 @@ subtest.deepEqual(result, expectedCollectionProperties, 'should return '+collectionName);

test.test('saveDatabase', (subtest) => {
test.test('saveDatabase', subtest => {
client.saveDatabase((err, result) => {

@@ -84,7 +84,8 @@ subtest.deepEqual(err, undefined, 'method should not return an error');

test.test('getCollection', (subtest) => {
client.getCollection(collectionName, (err, result) => {
test.test('getCollection', subtest => {
client.getCollection({ collection:collectionName }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');
subtest.deepEqual(result, expectedCollectionProperties, 'should return '+collectionName+' properties');
subtest.end();
end();
});

@@ -91,0 +92,0 @@ });

@@ -20,7 +20,7 @@ const ERROR_CODE_PARAMETER = -32602;

code: ERROR_CODE_PARAMETER,
message: 'insert: parameter \'Collection name\' is mandatory'
message: 'method "insert": property collection (alias col,c) is mandatory'
};
require('./client')(__filename, (test, client) => {
client.loadDatabase(dbName, (err, result) => {
require('./client')(__filename, (test, client, end) => {
client.loadDatabase({ database: dbName }, (err, result) => {

@@ -30,3 +30,3 @@ test.deepEqual(err, undefined, 'loadDatabase should not return any error');

client.addCollection(collectionName, (err, result) => {
client.addCollection({ collection:collectionName }, (err, result) => {

@@ -36,4 +36,4 @@ test.deepEqual(err, undefined, 'addCollection should not return any error');

test.test('insert should fail if collection name is null', (subtest) => {
client.insert(null, null, (err, result) => {
test.test('insert should fail if collection name is null', subtest => {
client.insert(null, (err, result) => {
subtest.deepEqual(err, expectedErr, `should return error ${expectedErr.message}`);

@@ -45,4 +45,4 @@ subtest.deepEqual(result, undefined, 'result should be undefined');

test.test('insert should fail if collection name is undefined', (subtest) => {
client.insert(undefined, null, (err, result) => {
test.test('insert should fail if collection name is undefined', subtest => {
client.insert(undefined, (err, result) => {
subtest.deepEqual(err, expectedErr, `should return error ${expectedErr.message}`);

@@ -54,4 +54,4 @@ subtest.deepEqual(result, undefined, 'result should be undefined');

test.test('insert should create the collection if it does not exist, then insert', (subtest) => {
client.insert(collectionNameNotExists, doc, (err, result) => {
test.test('insert should create the collection if it does not exist, then insert', subtest => {
client.insert({ collection:collectionNameNotExists, document:doc }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');

@@ -64,4 +64,4 @@ result.meta.created = typeof result.meta.created === 'number';

test.test('insert should return document', (subtest) => {
client.insert(collectionName, doc, (err, result) => {
test.test('insert should return document', subtest => {
client.insert({ collection:collectionName, document:doc }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');

@@ -74,6 +74,7 @@ result.meta.created = typeof result.meta.created === 'number';

test.test('insert without callback', (subtest) => {
client.insert(collectionName, doc);
test.test('insert without callback (lazy mode)', subtest => {
client.insert({ collection:collectionName, document: doc }, { lazy:true });
subtest.pass('pass');
subtest.end();
end();
});

@@ -80,0 +81,0 @@ });

@@ -5,4 +5,4 @@ const dbName = '__testInsert401_'+Date.now();

require('./client')(__filename, (test, client) => {
client.loadDatabase(dbName, (err, result) => {
require('./client')(__filename, (test, client, end) => {
client.loadDatabase({ database:dbName }, (err, result) => {

@@ -12,3 +12,3 @@ test.deepEqual(err, undefined, 'loadDatabase should not return any error');

client.addCollection(collectionName, (err, result) => {
client.addCollection({ collection:collectionName }, (err, result) => {

@@ -18,4 +18,4 @@ test.deepEqual(err, undefined, 'addCollection should not return any error');

test.test('insert should return undefined', (subtest) => {
client.insert(collectionName, doc, { sret:null }, (err, result) => {
test.test('insert should return undefined', subtest => {
client.insert({ collection:collectionName, document:doc, options:{ sret:null } }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');

@@ -27,4 +27,4 @@ subtest.deepEqual(result, undefined, 'should return undefined');

test.test('insert should return 1', (subtest) => {
client.insert(collectionName, doc, { sret:'01' }, (err, result) => {
test.test('insert should return 1', subtest => {
client.insert({ collection:collectionName, document:doc, options: { sret:'01' } }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');

@@ -36,4 +36,4 @@ subtest.deepEqual(result, 1, 'should return 1');

test.test('insert should return true', (subtest) => {
client.insert(collectionName, doc, { sret:'bool' }, (err, result) => {
test.test('insert should return true', subtest => {
client.insert({ collection:collectionName, document:doc, options:{ sret:'bool' } }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');

@@ -45,7 +45,8 @@ subtest.deepEqual(result, true, 'should return true');

test.test('insert should return id', (subtest) => {
client.insert(collectionName, doc, { sret:'id' }, (err, result) => {
test.test('insert should return id', subtest => {
client.insert({ collection:collectionName, document:doc, options:{ sret:'id' } }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');
subtest.deepEqual(typeof result, 'number', 'should return true');
subtest.end();
end();
});

@@ -52,0 +53,0 @@ });

@@ -21,4 +21,4 @@ const dbName = '__testGet_410_'+Date.now();

require('./client')(__filename, (test, client) => {
client.loadDatabase(dbName, (err, result) => {
require('./client')(__filename, (test, client, end) => {
client.loadDatabase({ database: dbName }, (err, result) => {

@@ -28,4 +28,4 @@ test.deepEqual(err, undefined, 'loadDatabase should not return any error');

test.test('insert should return document', (subtest) => {
client.insert(collectionName, doc, (err, result) => {
test.test('insert should return document', subtest => {
client.insert({ collection:collectionName, document:doc }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');

@@ -38,4 +38,4 @@ result.meta.created = typeof result.meta.created === 'number';

test.test('get should return error when collection does not exist', (subtest) => {
client.get('unexistingCollection', 1, (err, result) => {
test.test('get should return error when collection does not exist', subtest => {
client.get({ collection:'unexistingCollection', id:1 }, (err, result) => {
const expectedErr = {

@@ -51,4 +51,4 @@ code: ERROR_CODE_PARAMETER,

test.test('get should return document', (subtest) => {
client.get(collectionName, 1, (err, result) => {
test.test('get should return document', subtest => {
client.get({ collection:collectionName, id:1 }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');

@@ -61,7 +61,8 @@ result.meta.created = typeof result.meta.created === 'number';

test.test('get on a non existing document by id should return null', (subtest) => {
client.get(collectionName, 10, (err, result) => {
test.test('get on a non existing document by id should return null', subtest => {
client.get({ collection:collectionName, id:10 }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');
subtest.deepEqual(result, null, `should return error ${JSON.stringify(expectedErr)}`);
subtest.end();
end();
});

@@ -68,0 +69,0 @@ });

@@ -13,12 +13,17 @@ const dbName = '__testRemove_420_'+Date.now();

code: -32602,
message: 'remove: number of parameters should be at least 2'
message: 'method "remove": mandatory properties are missing (at least "collection, document" OR "collection, id")'
};
require('./client')(__filename, (test, client) => {
client.loadDatabase(dbName, (err, result) => {
const expectedErr3 = {
code: -32602,
message: 'method "remove": mandatory properties conflict, please specify properties "collection, document" OR "collection, id"'
};
require('./client')(__filename, (test, client, end) => {
client.loadDatabase({ database:dbName }, (err, result) => {
test.deepEqual(err, undefined, 'loadDatabase should not return any error');
test.deepEqual(typeof result, 'object', 'database loaded');
client.insert(collectionName, doc1, (err, result) => {
client.insert({ collection:collectionName, document:doc1 }, (err, result) => {

@@ -28,4 +33,4 @@ test.deepEqual(err, undefined, 'insert should not return any error');

test.test('remove a document by id should return removed document', (subtest) => {
client.remove(collectionName, 1, (err, result) => {
test.test('remove a document by id should return removed document', subtest => {
client.remove({ collection:collectionName, id:1 }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');

@@ -37,4 +42,4 @@ subtest.deepEqual(result, doc1, `should return ${JSON.stringify(doc1)}`);

test.test('remove a non existing document by id should return an error', (subtest) => {
client.remove(collectionName, 2, (err, result) => {
test.test('remove a non existing document by id should return an error', subtest => {
client.remove({ collection:collectionName, id:2 }, (err, result) => {
subtest.deepEqual(err, expectedErr1, `should return error ${JSON.stringify(expectedErr1)}`);

@@ -46,4 +51,4 @@ subtest.deepEqual(result, undefined, 'result should be undefined');

test.test('missing doc or id should return an error', (subtest) => {
client.remove(collectionName, (err, result) => {
test.test('missing document and id should return an error', subtest => {
client.remove({ collection:collectionName }, (err, result) => {
subtest.deepEqual(err, expectedErr2, `should return error ${JSON.stringify(expectedErr2)}`);

@@ -55,7 +60,16 @@ subtest.deepEqual(result, undefined, 'result should be undefined');

test.test('empty doc {} should return an error', (subtest) => {
client.remove(collectionName, {}, (err, result) => {
test.test('document and id specified should return an error', subtest => {
client.remove({ collection:collectionName, document:{}, id:1 }, (err, result) => {
subtest.deepEqual(err, expectedErr3, `should return error ${JSON.stringify(expectedErr3)}`);
subtest.deepEqual(result, undefined, 'result should be undefined');
subtest.end();
});
});
test.test('empty doc {} should return an error', subtest => {
client.remove({ collection:collectionName, document:{} }, (err, result) => {
subtest.deepEqual(err, expectedErr1, `should return error ${JSON.stringify(expectedErr1)}`);
subtest.deepEqual(result, undefined, 'result should be undefined');
subtest.end();
end();
});

@@ -62,0 +76,0 @@ });

@@ -18,4 +18,4 @@ const dbName = '__testFind_430_'+Date.now();

require('./client')(__filename, (test, client) => {
client.loadDatabase(dbName, (err, result) => {
require('./client')(__filename, (test, client, end) => {
client.loadDatabase({ database:dbName }, (err, result) => {

@@ -25,3 +25,3 @@ test.deepEqual(err, undefined, 'loadDatabase should not return any error');

client.insert(collectionName, doc, (err, result) => {
client.insert({ collection:collectionName, document: doc }, (err, result) => {

@@ -31,4 +31,4 @@ test.deepEqual(err, undefined, 'insert should not return any error');

test.test('find should return array of doc', (subtest) => {
client.find(collectionName, doc, (err, result) => {
test.test('find without filters should return array of documents', subtest => {
client.find({ collection:collectionName }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');

@@ -41,7 +41,17 @@ result[0].meta.created = typeof result[0].meta.created === 'number';

test.test('find should return [] if no result', (subtest) => {
client.find(collectionName, { foo:'bar2' }, (err, result) => {
test.test('find with filters should return array of documents', subtest => {
client.find({ collection:collectionName, filters:doc }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');
result[0].meta.created = typeof result[0].meta.created === 'number';
subtest.deepEqual(result, expected, `should return ${JSON.stringify(expected)}`);
subtest.end();
});
});
test.test('find should return [] if no result', subtest => {
client.find({ collection:collectionName, filters:{ foo:'bar2' } }, (err, result) => {
subtest.deepEqual(err, undefined, 'method should not return an error');
subtest.deepEqual(result, [], `should return ${JSON.stringify([])}`);
subtest.end();
end();
});

@@ -48,0 +58,0 @@ });

@@ -17,4 +17,4 @@ const dbName = '__testUpdate_440_'+Date.now();

require('./client')(__filename, (test, client) => {
client.loadDatabase(dbName, (err, result) => {
require('./client')(__filename, (test, client, end) => {
client.loadDatabase({ database:dbName }, (err, result) => {

@@ -24,3 +24,3 @@ test.deepEqual(err, undefined, 'loadDatabase should not return any error');

client.insert(collectionName, doc, (err, myDoc) => {
client.insert({ collection:collectionName, document:doc }, (err, myDoc) => {

@@ -30,7 +30,7 @@ test.deepEqual(err, undefined, 'insert should not return any error');

test.test('update should return new document', (subtest) => {
test.test('update should return new document', subtest => {
myDoc.foo = 'bar2';
client.update(collectionName, myDoc, (err, newDoc) => {
client.update({ collection:collectionName, document:myDoc }, (err, newDoc) => {
subtest.deepEqual(err, undefined, 'method should not return an error');

@@ -41,2 +41,3 @@ newDoc.meta.created = typeof newDoc.meta.created === 'number';

subtest.end();
end();
});

@@ -43,0 +44,0 @@ });

const tap = require('../tap');
const use = require('abrequire');
const Client = require('sloki-node-client');
const ENV = use('src/env');
const endpoint = require('../endpoints').tcp;
const Client = require('../../../clients/node');
const config = require('../../src/config');
const endpoints = require('../endpoints');
const path = require('path');
const engine = process.env.SLOKI_SERVER_ENGINE||'tcpbinary';
module.exports = (title, callback) => {
const tcpClient = new Client(endpoint, { applicationLayer:'jayson' });
const tcpClient = new Client(endpoints[engine], { engine });
function end() {
tcpClient.close();
}
tap.test(
path.basename(title),
{
timeout:ENV.DATABASES_AUTOSAVE_INTERVAL*3
},
(t) => {
{ timeout:config.DATABASES_AUTOSAVE_INTERVAL*3 },
t => {
tcpClient.on('error', (err) => {
tcpClient.on('error', err => {
t.fail('socket error', err);

@@ -26,5 +29,5 @@ t.end();

.connect()
.then((err) => {
t.deepEqual(err, undefined, 'should be connected');
callback(t, tcpClient);
.then(err => {
t.deepEqual(err, undefined, `should be connected (${engine})`);
callback(t, tcpClient, end);
});

@@ -34,8 +37,2 @@ }

tap.test('close client', (t) => {
tcpClient.close();
t.pass('client closed');
t.end();
process.exit(0);
});
};
SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc