AWESOME FEATURES AND MODERN API
ForerunnerDB is the only JavaScript database with a simple, rich JSON-based query language. Based on MongoDB’s query language and built with web applications in mind, ForerunnerDB will have you up and running in minutes.
Web database
Persistent storage across page loads, built-in basic and compound index support, easy JSON-based query language
Open-source
You can download, contribute and participate in the development of ForerunnerDB on GitHub
Developer-friendly
Cut down development time and project complexity with automatic data-binding and advanced views
Event hooks for your app
Listen for changes to data, react to document and view updates, create interactive data views
Browser-storage friendly
Works with browser storage to persist data as required by your application, falls back from IndexedDB -> WebSQL -> LocalStorage
Data-intensive applications
Build highly complex data-intensive applications by utilising a battle-tested production-ready web database
What is ForerunnerDB
ForerunnerDB is a NoSQL JavaScript JSON database with a query language based on MongoDB (with some differences) and runs on browsers and Node.js. It is in use in many large production web applications and is transparently used by over 6 million clients. ForerunnerDB is the most advanced, battle-tested and production ready browser-based JSON database system available today.
What is ForerunnerDB’s Primary Use Case?
ForerunnerDB was created primarily to allow web (and mobile web / hybrid) application developers to easily store, query and manipulate JSON data in the browser / mobile app via a simple query language, making handling JSON data significantly easier.
ForerunnerDB supports data persistence on both the client (via LocalForage) and in Node.js (by saving and loading JSON data files).
If you build advanced web applications with AngularJS or perhaps your own framework or if you are looking to build a server application / API that needs a fast queryable in-memory store with file-based data persistence and a very easy setup (simple installation via NPM and no requirements except Node.js) you will also find ForerunnerDB very useful.
An example hybrid application that runs on iOS, Android and Windows Mobile via Ionic (AngularJS + Cordova with some nice extensions) is available in this repository under the ionicExampleClient folder. See here for more details.
Download
NPM
If you are using Node.js (or have it installed) you can use NPM to download ForerunnerDB via:
npm install forerunnerdb
NPM Dev Builds
You can also install the development version which usually includes new features that are considered either unstable or untested. To install the development version you can ask NPM for the dev tag:
npm install forerunnerdb --tag dev
Bower
You can also install ForerunnerDB via the bower package manager:
bower install forerunnerdb
No Package Manager
If you are still a package manager hold-out or you would prefer a more traditional download, please click here.
How to Use
Use ForerunnerDB in Browser
fdb-all.min.js is the entire ForerunnerDB with all the added extras. If you prefer only the core database functionality (just collections, no views etc) you can use fdb-core.min.js instead. A list of the different builds is available for you to select the best build for your purposes.
Include the fdb-all.min.js file in your HTML (change path to the location you put forerunner):
<script src="./js/dist/fdb-all.min.js" type="text/javascript"></script>
Use ForerunnerDB in Node.js
After installing via npm (see above) you can require ForerunnerDB in your code:
var ForerunnerDB = require("forerunnerdb");
var fdb = new ForerunnerDB();
Create a Database
var db = fdb.db("myDatabaseName");
If you do not specify a database name a randomly generated one is provided instead.
Collections (Tables)
Data Binding: Enabled
To create or get a reference to a collection object, call db.collection (where collectionName is the name of your collection):
var collection = db.collection("collectionName");
In our examples we will use a collection called “item" which will store some fictitious items for sale:
var itemCollection = db.collection("item");
Auto-Creation
When you request a collection that does not yet exist it is automatically created. If it already exists you are given the reference to the existing collection. If you want ForerunnerDB to throw an error if a collection is requested that does not already exist you can pass an option to the collection() method instead:
var collection = db.collection("collectionName", {autoCreate: false});
Specifying a Primary Key Up-Front
If no primary key is specified ForerunnerDB uses “_id" by default.
On requesting a collection you can specify a primary key that the collection should be using. For instance to use a property called “name" as the primary key field:
var collection = db.collection("collectionName", {primaryKey: "name"});
You can also read or specify a primary key after instantiation via the primaryKey() method.
Capped Collections
Occasionally it is useful to create a collection that will store a finite number of records. When that number is reached, any further documents inserted into the collection will cause the oldest inserted document to be removed from the collection on a first-in-first-out rule (FIFO).
In this example we create a capped collection with a document limit of 5:
var collection = db.collection("collectionName", {capped: true, size: 5});
Inserting Documents
If you do not specify a value for the primary key, one will be automatically generated for any documents inserted into a collection. Auto-generated primary keys are pseudo-random 16 character strings.
PLEASE NOTE: When doing an insert into a collection, ForerunnerDB will automatically split the insert up into smaller chunks (usually of 100 documents) at a time to ensure the main processing thread remains unblocked. If you wish to be informed when the insert operation is complete you can pass a callback method to the insert call. Alternatively you can turn off this behaviour by calling yourCollection.deferredCalls(false);
You can either insert a single document object:
itemCollection.insert({
_id: 3,
price: 400,
name: "Fish Bones"
});
or pass an array of documents:
itemCollection.insert([{
_id: 4,
price: 267,
name:"Scooby Snacks"
}, {
_id: 5,
price: 234,
name: "Chicken Yum Yum"
}]);
Inserting a Large Number of Documents
When inserting large amounts of documents ForerunnerDB may break your insert operation into multiple smaller operations (usually of 100 documents at a time) in order to avoid blocking the main processing thread of your browser / Node.js application. You can find out when an insert has completed either by passing a callback to the insert call or by switching off async behaviour.
Passing a callback:
itemCollection.insert([{
_id: 4,
price: 267,
name:"Scooby Snacks"
}, {
_id: 5,
price: 234,
name: "Chicken Yum Yum"
}], function (result) {
// The result object will contain two arrays (inserted and failed)
// which represent the documents that did get inserted and those
// that didn't for some reason (usually index violation). Failed
// items also contain a reason. Inspect the failed array for further
// information.
});
If you wish to switch off async behaviour you can do so on a per-collection basis via:
db.collection('myCollectionName').deferredCalls(false);
After async behaviour (deferred calls) has been disabled, you can insert records and be sure that they will all have inserted before the next statement is processed by the application’s main thread.
Inserting Special Objects
JSON has limitations on the types of objects it will serialise and de-serialise back to an object. Two very good examples of this are the Date() and RegExp() objects. Both can be serialised via JSON.stringify() but when calling JSON.parse() on the serialised version neither type will be “re-materialised" back to their object representations.
For example:
var a = {
dt: new Date()
};
a.dt instanceof Date; // true
var b = JSON.stringify(a); // "{"dt":"2016-02-11T09:52:49.170Z"}"
var c = JSON.parse(b); // {dt: "2016-02-11T09:52:49.170Z"}
c.dt instanceof Date; // false
As you can see, parsing the JSON string works but the dt key no longer contains a Date instance and only holds the string representation of the date. This is a fundamental drawback of using JSON.stringify() and JSON.parse() in their native form.
If you want ForerunnerDB to serialise / de-serialise your object instances you must use this format instead:
var a = {
dt: fdb.make(new Date())
};
By wrapping the new Date() in fdb.make() we allow ForerunnerDB to provide the Date() object with a custom .toJSON() method that serialises it differently to the native implementation.
For convenience the make() method is also available on all ForerunnerDB class instances e.g. db, collection, view etc. For instance you can access make via:
var fdb = new ForerunnerDB(),
db = fdb.db('test'),
coll = db.collection('testCollection'),
date = new Date();
// All of these calls will do the same thing:
date = fdb.make(date);
date = db.make(date);
date = coll.make(date);
You can read more about how ForerunnerDB’s serialiser works here.
Supported Instance Types and Usage
Date
var a = {
dt: fdb.make(new Date())
};
RegExp
var a = {
re: fdb.make(new RegExp(".*", "i"))
};
or
var a = {
re: fdb.make(/.*/i))
};
Adding Custom Types to the Serialiser
ForerunnerDB’s serialisation system allows for custom type handling so that you can expand JSON serialisation to your own custom class instances.
This can be a complex topic so it has been broken out into the Wiki section for further reading here.
Searching the Collection
PLEASE NOTE While we have tried to remain as close to MongoDB’s query language as possible, small differences are present in the query matching logic. The main difference is described here: Find behaves differently from MongoDB
See the Special Considerations section for details about how names of keys / properties in a query object can affect a query’s operation.
Much like MongoDB, searching for data in a collection is done using the find() method, which supports many of the same operators starting with a $ that MongoDB supports. For instance, finding documents in the collection where the price is greater than 90 but less than 150, would look like this:
itemCollection.find({
price: {
"$gt": 90,
"$lt": 150
}
});
And would return an array with all matching documents. If no documents match your search, an empty array is returned.
Regular Expressions
Searches support regular expressions for advanced text-based queries. Simply pass the regular expression object as the value for the key you wish to search, just like when using regular expressions with MongoDB.
Insert a document:
collection.insert([{
"foo": "hello"
}]);
Search by regular expression:
collection.find({
"foo": /el/
});
You can also use the RegExp object instead:
var myRegExp = new RegExp("el");
collection.find({
"foo": myRegExp
});
Query Operators
ForerunnerDB supports many of the same query operators that MongoDB does, and adds some that are not available in MongoDB but which can help in browser-centric applications.
- $gt Greater Than
- $gte Greater Than / Equal To
- $lt Less Than
- $lte Less Than / Equal To
- $eq Equal To (==)
- $eeq Strict Equal To (===)
- $ne Not Equal To (!=)
- $nee Strict Not Equal To (!==)
- $in Match Any Value In An Array Of Values
- $fastIn Match Any String or Number In An Array Of String or Numbers
- $nin Match Any Value Not In An Array Of Values
- $distinct Match By Distinct Key/Value Pairs
- $count Match By Length Of Sub-Document Array
- $or Match any of the conditions inside the sub-query
- $and Match all conditions inside the sub-query
- $exists Check that a key exists in the document
- $elemMatch Limit sub-array documents by query
- $elemsMatch Multiple document version of $elemMatch
- $aggregate Converts an array of documents into an array of values base on a path / key
- $near Geospatial operation finds outward from a central point
$gt
Selects those documents where the value of the field is greater than (i.e. >) the specified value.
{ field: {$gt: value} }
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]);
result = coll.find({
val: {
$gt: 1
}
});
Result is:
[{
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]
$gte
Selects the documents where the value of the field is greater than or equal to (i.e. >=) the specified value.
{ field: {$gte: value} }
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]);
result = coll.find({
val: {
$gte: 1
}
});
Result is:
[{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]
$lt
Selects the documents where the value of the field is less than (i.e. <) the specified value. [/kc_column_text][kc_raw_code code="PHByZT48Y29kZSBjbGFzcz0ibGFuZ3VhZ2UtamF2YXNjcmlwdCI+eyBmaWVsZDogeyAkbHQ6IHZhbHVlfSB9PC9jb2RlPjwvcHJlPg==" _id="246566"][kc_column_text _id="122167"]
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]);
result = coll.find({
val: {
$lt: 2
}
});
Result is:
[{
_id: 1,
val: 1
}]
$lte
Selects the documents where the value of the field is less than or equal to (i.e. <=) the specified value. [/kc_column_text][kc_raw_code code="PHByZT48Y29kZSBjbGFzcz0ibGFuZ3VhZ2UtamF2YXNjcmlwdCI+eyBmaWVsZDogeyAkbHRlOiB2YWx1ZX0gfTwvY29kZT48L3ByZT4=" _id="639094"][kc_column_text _id="581885"]
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]);
result = coll.find({
val: {
$lte: 2
}
});
Result is:
[{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}]
$eq
Selects the documents where the value of the field is equal (i.e. ==) to the specified value.
{field: {$eq: value} }
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]);
result = coll.find({
val: {
$eq: 2
}
});
Result is:
[{
_id: 2,
val: 2
}]
$eeq
Selects the documents where the value of the field is strict equal (i.e. ===) to the specified value. This allows for strict equality checks for instance zero will not be seen as false because 0 !== false and comparing a string with a number of the same value will also return false e.g. (‘2’ == 2) is true but (‘2’ === 2) is false.
{field: {$eeq: value} }
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: "2"
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: "2"
}]);
result = coll.find({
val: {
$eeq: 2
}
});
Result is:
[{
_id: 2,
val: 2
}]
$ne
Selects the documents where the value of the field is not equal (i.e. !=) to the specified value. This includes documents that do not contain the field.
{field: {$ne: value} }
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]);
result = coll.find({
val: {
$ne: 2
}
});
Result is:
[{
_id: 1,
val: 1
}, {
_id: 3,
val: 3
}]
$nee
Selects the documents where the value of the field is not equal equal (i.e. !==) to the specified value. This allows for strict equality checks for instance zero will not be seen as false because 0 !== false and comparing a string with a number of the same value will also return false e.g. (‘2’ != 2) is false but (‘2’ !== 2) is true. This includes documents that do not contain the field.
{field: {$nee: value} }
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]);
result = coll.find({
val: {
$nee: 2
}
});
Result is:
[{
_id: 1,
val: 1
}, {
_id: 3,
val: 3
}]
$in
If your field is a string or number and your array of values are also either strings or numbers you can utilise $fastIn which is an optimised $in query that uses indexOf() to identify matching values instead of looping over all items in the array of values and running a new matching process against each one. If your array of values include sub-queries or other complex logic you should use $in, not $fastIn.
Selects documents where the value of a field equals any value in the specified array.
{ field: { $in: [, , ... ] } }
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]);
result = coll.find({
val: {
$in: [1, 3]
}
});
Result is:
[{
_id: 1,
val: 1
}, {
_id: 3,
val: 3
}]
$fastIn
You can use $fastIn instead of $in when your field contains a string or number and your array of values contains only strings or numbers. $fastIn utilises indexOf() to speed up performance of the query. This means that the array of values is not evaluated for sub-queries, other operators like $gt etc, and it is assumed that the array of values is a completely flat array, filled only with strings or numbers.
Selects documents where the string or number value of a field equals any string or number value in the specified array.
The array of values MUST be a flat array and contain only strings or numbers.
{ field: { $fastIn: [, , ... ] } }
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]);
result = coll.find({
val: {
$fastIn: [1, 3]
}
});
Result is:
[{
_id: 1,
val: 1
}, {
_id: 3,
val: 3
}]
$nin
Selects documents where the value of a field does not equal any value in the specified array.
{ field: { $nin: [ , ... ]} }
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]);
result = coll.find({
val: {
$nin: [1, 3]
}
});
Result is:
[{
_id: 2,
val: 2
}]
$distinct
Selects the first document matching a value of the specified field. If any further documents have the same value for the specified field they will not be returned.
{ $distinct: { field: 1 } }
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 1
}, {
_id: 3,
val: 1
}, {
_id: 4,
val: 2
}]);
result = coll.find({
$distinct: {
val: 1
}
});
Result is:
[{
_id: 1,
val: 1
}, {
_id: 4,
val: 2
}]
$count
Version >= 1.3.326
This is equivalent to MongoDB’s $size operator but please see below for usage.
Selects documents based on the length (count) of items in an array inside a document.
{ $count: { field: } }
Select Documents Where The “arr" Array Field Has Only 1 Item
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
arr: []
}, {
_id: 2,
arr: [{
val: 1
}]
}, {
_id: 3,
arr: [{
val: 1
}, {
val: 2
}]
}]);
result = coll.find({
$count: {
arr: 1
}
});
Result is:
[{
_id: 2,
arr: [{
val: 1
}]
}]
Select Documents Where The “arr" Array Field Has More Than 1 Item
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
arr: []
}, {
_id: 2,
arr: [{
val: 1
}]
}, {
_id: 3,
arr: [{
val: 1
}, {
val: 2
}]
}]);
result = coll.find({
$count: {
arr: {
$gt: 1
}
}
});
Result is:
[{
_id: 3,
arr: [{
val: 1
}, {
val: 2
}]
}]
$or
The $or operator performs a logical OR operation on an array of two or more and selects the documents that satisfy at least one of the .
{ $or: [ { }, { }, ... , { } ] }
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]);
result = coll.find({
$or: [{
val: 1
}, {
val: {
$gte: 3
}
}]
});
Result is:
[{
_id: 1,
val: 1
}, {
_id: 3,
val: 3
}]
$and
Performs a logical AND operation on an array of two or more expressions (e.g. , , etc.) and selects the documents that satisfy all the expressions in the array. The $and operator uses short-circuit evaluation. If the first expression (e.g. ) evaluates to false, ForerunnerDB will not evaluate the remaining expressions.
{ $and: [ { }, { } , ... , { } ] }
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]);
result = coll.find({
$and: [{
_id: 3
}, {
val: {
$gte: 3
}
}]
});
Result is:
[{
_id: 3,
val: 3
}]
$exists
When is true, $exists matches the documents that contain the field, including documents where the field value is null. If is false, the query returns only the documents that do not contain the field.
{ field: { $exists: } }
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 2,
moo: "hello"
}, {
_id: 3,
val: 3
}]);
result = coll.find({
moo: {
$exists: true
}
});
Result is:
[{
_id: 2,
val: 2,
moo: "hello"
}]
Projection
$elemMatch
The $elemMatch operator limits the contents of an array field from the query results to contain only the first element matching the $elemMatch condition.
The $elemMatch operator is specified in the options object of the find call rather than the query object.
MongoDB $elemMatch Documentation
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert({
names: [{
_id: 1,
text: "Jim"
}, {
_id: 2,
text: "Bob"
}, {
_id: 3,
text: "Bob"
}, {
_id: 4,
text: "Anne"
}, {
_id: 5,
text: "Simon"
}, {
_id: 6,
text: "Uber"
}]
});
result = coll.find({}, {
$elemMatch: {
names: {
text: "Bob"
}
}
});
Result is:
{
names: [{
_id: 2,
text: "Bob"
}]
}
Notice that only the FIRST item matching the $elemMatch clause is returned in the names array. If you require multiple matches use the ForerunnerDB-specific $elemsMatch operator instead.
$elemsMatch
The $elemsMatch operator limits the contents of an array field from the query results to contain only the elements matching the $elemMatch condition.
The $elemsMatch operator is specified in the options object of the find call rather than the query object.
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert({
names: [{
_id: 1,
text: "Jim"
}, {
_id: 2,
text: "Bob"
}, {
_id: 3,
text: "Bob"
}, {
_id: 4,
text: "Anne"
}, {
_id: 5,
text: "Simon"
}, {
_id: 6,
text: "Uber"
}]
});
result = coll.find({}, {
$elemsMatch: {
names: {
text: "Bob"
}
}
});
Result is:
{
names: [{
_id: 2,
text: "Bob"
}, {
_id: 3,
text: "Bob"
}]
}
Notice that all items matching the $elemsMatch clause are returned in the names array. If you require match on ONLY the first item use the MongoDB-compliant $elemMatch operator instead.
$aggregate
Coverts an array of documents into an array of values that are derived from a key or path in the documents. This is very useful when combined with the $find operator to run sub-queries and return arrays of values from the results.
{ $aggregate: path}
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
_id: 1,
val: 1
}, {
_id: 2,
val: 2
}, {
_id: 3,
val: 3
}]);
result = coll.find({}, {
$aggregate: "val"
});
Result is:
[1, 2, 3]
$near
PLEASE NOTE: BETA STATUS – PASSES UNIT TESTING BUT MAY BE UNSTABLE
Finds other documents whose co-ordinates based on a 2d index are within the specified distance from the specified centre point. Co-ordinates must be presented in latitude / longitude for $near to work.
{
	field: {
		$near: {
			$point: [<latitude number>, <longitude number>],
			$maxDistance: <number>,
			$distanceUnits: <units string>
		}
	}
}
Usage
var fdb = new ForerunnerDB(),
db = fdb.db("test"),
coll = db.collection("test");
coll.insert([{
latLng: [51.50722, -0.12750],
name: 'Central London'
}, {
latLng: [51.525745, -0.167550], // 2.18 miles
name: 'Marylebone, London'
}, {
latLng: [51.576981, -0.335091], // 10.54 miles
name: 'Harrow, London'
}, {
latLng: [51.769451, 0.086509], // 20.33 miles
name: 'Harlow, Essex'
}]);
// Create a 2d index on the lngLat field
coll.ensureIndex({
latLng: 1
}, {
type: '2d'
});
// Query index by distance
// $near queries are sorted by distance from centre point by default
result = coll.find({
latLng: {
$near: {
$point: [51.50722, -0.12750],
$maxDistance: 3,
$distanceUnits: 'miles'
}
}
});
Result is:
[{
"lngLat": [51.50722, -0.1275],
"name": "Central London",
"_id": "1f56c0b5885de40"
}, {
"lngLat": [51.525745, -0.16755],
"name": "Marylebone, London",
"_id": "372a34d9f17fbe0"
}]
Download/Documentation: https://github.com/ForerunnerDB/
0 Comments