README
[TOC]
sock-rpc-stats
- This a Node.js package that implements a key-value store server for use with Arma 3 sock extension.
Changelog
- 0.0.16
- Update sock-rpc dependency to v1.0.8
- 0.0.15
- Require ES6 Generators (node >=0.11.6)
- Remove dependency on wait.for (fibers)
- 0.0.14
- Drop support for MongoDB
- Update sock-rpc dependency to v1.0.7
- 0.0.13
- Update sock-rpc dependency to v1.0.6
- 0.0.12
- Update sock-rpc dependency to v1.0.5
- 0.0.11
- Update sock-rpc dependency to v1.0.4
- 0.0.10
- Allow keys(), and get() commands to get list of all active scopes through the RPC API
- Add Client class for communicating with remote sock-rpc-stats servers
- 0.0.9
- Enhancement: Add support for commands: merge, keys, and count
- 0.0.8
- Enhancement: Add support for wiping all keys in a scope
- 0.0.5
- Defect: Fix major problem where the interval function was always disabled
- Defect: Fix issue with default values 0, and empty string, being treated as undefined
- 0.0.4
- Enhancement: add support for pop, push, shift, and unshift array operations
- Enhancement: add support for dot notation on keys
- 0.0.3
- Defect: Fixed issue with line endings for Linux
- 0.0.2
- Enhancement: meta function added for viewing scope meta-data
- 0.0.1 - Initial release
The Key-Value Store
The key-value store is essentially a big hash-map, with smaller nested hash-maps (referred to as scopes).
- keys are always strings.
- values can be strings, numbers, null, arrays, or objects.
.
Here is a sample view of the key-value store with 2 scopes (i.e. "player1_id"
, and "player2_id"
):
{
"player1_id" : {
"name": "player1",
"money": 1000
},
"player2_id" : {
"name": "player2",
"money": 3000
}
}
How to Use
sock-rpc-stats --url=<string> [--container=<string>] [-i=<interval>] [--host=<listen_host>] [--port=<listen_port>] [--help]
--url
) syntax
Supported storage systems, and connection URL (- File System --url=file://path/to/directory
- MySQL (>= 5.5.38) --url=mysql://user:pass@host:port/database
- Cassandra (>= 1.2) --url=cassandra://user:pass@host1,host2,etc:port/keySpace
- CouchDB (>= 1.5.0) --url=http://user:pass@host:port/database
- Redis (>= 2.8.4) --url=redis://:pass@host:port/database
--container
)
Specifying database container (For some storage systems, a container name is needed. By default the value "scopes" is used as the container name.
- For File System, this is ignored.
- For MySQL, this is the name used for the SQL table.
- For Cassandra, this is the name used for the CQL3 table.
- For CouchDB, this is ignored
- For Redis, this the prefix used for the hash keys
You do not need to create the container, it is automatically created if it does not exist. (For MySQL, the username used for connecting must have CREATE TABLE
permission).
--interval
In-Memory read/write and save All active scope data is kept in memory. A scope is said to be active once it has been read from the storage system. After that point, all the read/write operations that are done through the RPC API only affect the data that is in-memory. This is where --interval
comes in. It allows you to specify how often (default is every 60 seconds) the data from memory is saved to the storage system.
Note that not all scopes might be saved on every iteration. The server only saves the scopes that have been modified since the last save operation.
--backup
)
Automatic backup (This option is set to true
by default. Every time the data for a scope is read from the storage system, it is automatically backed-up under a new scope. For example if the original scope name is "player1"
, the backed-up scope name would be:
"player1" + (new Date()).getTime() + ".bak"
This might appear excessive, to create backups every time a scope is read from the database. However, note that once data is read from the storage system into memory, then it's not read back again.
Other options
--repl
enable Node.js Read-Eval-Print-Loop(REPL) [default: false]--host
IP address to listen on for client stats requests [default: "localhost"]--port
port number to listen on for client stats requests [default: 1337]
RPC API - Core functions
These are the functions for setting and getting key-value pairs. When you set or get a key-value pair, you always do it in the context of a specific scope:
get("scope")
This function allows the client to get all the key-values pairs within the specified scope.
Example client SQF call:
//returns {[["key1", "value1"], ["key2", "value2"], ...]} on success, or nil on failure
[(getPlayerUID player)] call stats_get;
get("scope", "key1", default)
- default (optional) - Value to return, if the key is not found
This function allows the client to get the value of a key within a specific scope.
Example client SQF call:
//returns the value (or default) associated with the key
[(getPlayerUID player), "name"] call stats_get;
get("scope", ["key1", default], ...)
- default (optional) - Value to use, if the key is not found
This function allows the client to get the value of one or more keys within a specific scope.
Example client SQF call:
//returns [["key1", "value1"], ["key2", "value2"], ...] on success, or nil on failure
[(getPlayerUID player), ["key1", "default1"], ["key2", "default2"]] call stats_get;
set("scope", "key1", "value1")
This function allows the client to set a key-value pair within a specific scope.Example client SQF call:
//returns true on success, false on failure
[(getPlayerUID player), "name", (getPlayerName player)] call stats_set;
set("scope", ["key1", "value1"], ...)
This function allows the client to set one or more key-value pairs within a specific scope.
Example client SQF call:
//returns true on success, or false on failure
[(getPlayerUID player), ["key1", "value1"], ["key2", "value2"]] call stats_set;
flush("scope",...)
This function allows the client to flush one or more scopes to the database. Once flushed, the in-memory data is saved, and the scope is removed from memory.
Example client SQF call:
//returns number of scopes that were saved, or nil on failure
[(getPlayerUID player)] call stats_flush;
wipe("scope")
This function allows the client to wipe all the keys within a specific scope.
Example client SQF call:
//returns true on success, false on failure
[(getPlayerUID player)] call stats_wipe;
count("scope")
This function allows the client to count the child keys within a specific scope.
Example client SQF call:
//returns the count of child keys
[(getPlayerUID player)] call stats_count;
count("scope", "key1", default)
- default (optional) - Value to return, if the key is not found
This function allows the client to count the child keys at the specified key within a specific scope.
Example client SQF call:
//returns the count (or default) of child keys
[(getPlayerUID player), "name"] call stats_count;
count("scope", ["key1", default], ...)
- default (optional) - Value to use, if the key is not found
This function allows the client to count the child keys at the specified keys within a specific scope.
Example client SQF call:
//returns [["key1", 3], ["key2", -1], ...] on success, or nil on failure
[(getPlayerUID player), ["key1", -1], ["key2", -1]] call stats_count;
keys("scope")
This function allows the client to retrieve the child keys within a specific scope.
Example client SQF call:
//returns ["key1", "key2",...]
[(getPlayerUID player)] call stats_keys;
keys("scope", "key1", default)
- default (optional) - Value to return, if the key is not found
This function allows the client to retrieve the child keys at the specified key within a specific scope.
Example client SQF call:
//returns ["a", "b",..] or default value
[(getPlayerUID player), "name"] call stats_keys;
keys("scope", ["key1", default], ...)
- default (optional) - Value to use, if the key is not found
This function allows the client to retrieve the child keys at the specified keys within a specific scope.
Example client SQF call:
//returns [["key1", ["a", "b"]], ["key2", nil], ...] on success, or nil on failure
[(getPlayerUID player), ["key1", []], ["key2", nil]] call stats_keys;
merge("scope", "key", value)
This function allows the client to merge a value (usually a hash) with the existing value at the specified key within a specific scope.
Example client SQF call:
/*
* returns true on success, false on failure (e.g. if the key does not exist)
* Note the usage of the SQF sock_hash function to create a hash/object since SQF has no native hash data type.
*/
private["_hash1"];
_hash1 = [
["a", 1],
["b", 2]
] call sock_hash;
[(getPlayerUID player), "key1", _hash1] call stats_merge;
merge("scope", ["key", value], ...)
This function allows the client to merge one or more values (usually hashes) with the existing values at the specified keys within a specific scope.
Example client SQF call:
//returns true on success, false on failure
private["_hash1", "_hash2"];
_hash1 = [
["a", 1],
["b", 2]
] call sock_hash;
_hash2 = [
["c", 3],
["d", 4]
] call sock_hash;
[(getPlayerUID player), ["key1",_hash1], ["key2", _hash2]] call stats_merge;
RPC API - Array-only manipulation functions
pop("scope", "key1", default)
- default (optional) - Value to return, if the key is not found
This function allows the client to pop the last element of the array at the specified key within a specific scope.
Example client SQF call:
//returns the value (or default) associated with the key
[(getPlayerUID player), "name"] call stats_pop;
pop("scope", ["key1", default], ...)
- default (optional) - Value to use, if the key is not found
This function allows the client to pop one or more values of arrays at the specified keys within a specific scope.
Example client SQF call:
//returns [["key1", "value1"], ["key2", "value2"], ...] on success, or nil on failure
[(getPlayerUID player), ["key1", "default1"], ["key2", "default2"]] call stats_pop;
shift("scope", "key1", default)
- default (optional) - Value to return, if the key is not found
This function allows the client to shift the first element of the array at the specified key within a specific scope.
Example client SQF call:
//returns the value (or default) associated with the key
[(getPlayerUID player), "name"] call stats_shift;
shift("scope", ["key1", default], ...)
- default (optional) - Value to use, if the key is not found
This function allows the client to shift one or more values of arrays at the specified keys within a specific scope.
Example client SQF call:
//returns [["key1", "value1"], ["key2", "value2"], ...] on success, or nil on failure
[(getPlayerUID player), ["key1", "default1"], ["key2", "default2"]] call stats_shift;
push("scope", "key1", "value1")
This function allows the client to push a a value into the array at the specified key within a given scope.
Example client SQF call:
//returns true on success, false on failure
[(getPlayerUID player), "name", (getPlayerName player)] call stats_push;
push("scope", ["key1", "value1"], ...)
This function allows the client to push one or more values into the arrays at specified keys within the given scope.
Example client SQF call:
//returns true on success, or false on failure
[(getPlayerUID player), ["key1", "value1"], ["key2", "value2"]] call stats_push;
unshift("scope", "key1", "value1")
This function allows the client to unshift a a value into the array at the specified key within a given scope.
Example client SQF call:
//returns true on success, false on failure
[(getPlayerUID player), "name", (getPlayerName player)] call stats_unshift;
unshift("scope", ["key1", "value1"], ...)
This function allows the client to unshift one or more values into the arrays at the specified keys within the given scope.
Example client SQF call:
//returns true on success, or false on failure
[(getPlayerUID player), ["key1", "value1"], ["key2", "value2"]] call stats_unshift;
REPL (Read-Eval-Print Loop)
When starting the server, you can use the --repl
parameter. This will bring up Node.js REPL augmented with the same functions that are available in the RPC API. This is useful for browsing and manipulating the data store.
> help()
// Get the names of all active scopes
get();
// Get all keys from a specific scope
get("scope");
// Get the value of a key from a specific scope
get("scope", "key1", default);
// Get the value of one or more keys from a specific scope
get("scope", ["key1", default], ...);
// Update/Insert the given key-value pair within a specific scope
set("scope", "key", value);
// Update/Insert one or more key-value pairs within a specific scope
set("scope", ["key", value], ...);
// remove, and return the last element of the array at the specified key
pop("scope", "key1", default);
// remove, and return the last element of one or more arrays at the specified keys
pop("scope", ["key1", default], ...);
// remove, and return the first element the array at the specified key
shift("scope", "key1", default);
// remove, and return the first element of one or more arrays
shift("scope", ["key1", default], ...);
// count the number of child keys within the specified scope
count("scope");
// count the number of child keys within the specified key
count("scope", "key1", default);
// count the number of child keys for one or more specified keys
count("scope", ["key1", default], ...);
// get an array with the names of child keys within the specified scope
keys("scope");
// get an array with the names of child keys within the specified key
keys("scope", "key1", default);
// get an array with the names of child keys, for one ore more specified keys
keys("scope", ["key1", default], ...);
// append the value to the end of the array at the specified key
push("scope", "key1", value);
// append the one or more values to the end of the arrays at the specified keys
push("scope", ["key1", value], ...);
// prepend the value to the beginning of the array at the specified key
unshift("scope", "key1", value);
// prepend the one or more values to the beginning of the arrays at the specified keys
unshift("scope", ["key1", value], ...);
// merges the value with the data at the specified key
merge("scope", "key1", value);
// merge the one or more values with the data at the specified keys
merge("scope", ["key1", value], ...);
// Save one or more scopes, and delete them from memory
flush("scope", ...);
// Wipes all the keys of a specific scope
wipe("scope");
// Get meta-data for a specific active scope
meta("scope");
// Show help for all sections
help();
// Show help for specific sections
help("section", ...);
Note that, unlike the RPC API, when using the REPL, all write operations are immediately sent to the storage system.