<commit_before>'use strict';

var util = require('./lib/util');

exports = module.exports = Mase;

/**
### README

The module exports a constructor.

```js
var Mase = require('mase');
var db = new Mase(string name, array data)
```

Memory dbs with the same `name` share memory.
It can take an array of `data` as a second or first argument.
If so, `data` is upserted into the db by only using each
document's `_id` as a selector.

### spec
```js
function Mase([string name, array data])
```

_arguments_
- `name` type string, name for the collection
- `data` type array to insert in memory db

_defaults_
- `name` to a random 24 length string using `[A-Za-z0-9]` characters
- `data`
  - to the previous collection `name` array if there was one
  - to empty array if collection `name` it wasn't

_returns_ a new instance

_instance properties_
- `store` array of data of the instance

**/

// dbs namespace
var mase = {};

function Mase(name, data){
  if(!(this instanceof Mase)){
    return new Mase(name, data);
  }

  data = data || name;
  this.name = util.type(name).string || util.randomID();
  if(mase[this.name]){ return mase[this.name]; }

  this.store = [];
  mase[this.name] = this;

  if(util.type(data).array){
    data.forEach(function(item){
      this.update({_id: item._id}, item, {$upsert: true});
    }, this);
  }
}

/**
## insert

Insert a new document. When `document` is not a plain object,
i.e. `'one', 1, etc.`, it will be inserted using its value.

```js
db.insert(1); // => {value: 1, _id: 'wZy7rd33Q8478pvi'}
```

### spec
```js
function insert(object|any document)
```

_arguments_
 - `document` type object or any, element to insert.

_defaults_
 - `document._id` to a 24 length random string

_returns_ a clone of the inserted `document`

**/
Mase.prototype.insert = function(_doc){
  var doc = util.clone(util.type(_doc).plainObject || {value: _doc}, true);
  if(doc._id === void 0){ doc._id = util.randomID(); }
  this.changed('insert', doc, null);
  this.store.push(doc);
  return doc;
};

/**
## find

Find documents. By default, documents are tested
for value equality with [`lodash.isEqual`][p-lodash.isEqual].
This can be changed using a `$test` function.

### spec
```js
function find([object fields, object options|function $test])
```

_arguments_
 - `fields` type object, document fields for lookup
 - `$test` type function that will test `fields` for each document
 - `options` type object, helper object on how to do the lookup

_options_ properties
 - `$acc` type boolean, return value of the `$test` function
 - `$test` type function that tests `fields` against each document
 - `$break` type boolean, wether to **break the search after a match**
 - `$count` type boolean, wether to have `result` start as `0` or `[]`
 - `$result` type array or number, result returned. It will start as:
  - empty array when `$count` is falsy
  - `0` when `$count` is truthy

`$test(fields, doc, key, options)` _arguments_
 - `fields` object fields given as argument
 - `doc` document object found
 - `key` property of `fields` to be tested
 - `options` the options object passed in to `find`

_defaults_
- `options.$test` an equality test using [`lodash.isEqual`][p-lodash.isEqual]

_returns_ `o.$result`

[p-lodash.isEqual]: https://lodash.com/docs#isEqual
**/
Mase.prototype.find = function(fields, o){
  if(fields && !util.type(fields).match(/function|plainObject/)){
    throw new TypeError('find([fields, object options|function $test]) '
      + '`fields` should be function or plainObject'
    );
  }

  var spec = Object.keys(fields || {});
  var length = this.store.length;
  o = util.type(o).plainObject || {$test: o || fields};

  if(!spec.length){ spec = null; }
  if(!length || (!spec && typeof o.$test !== 'function')){
    return o.$count ? length : util.clone(this.store, true);
  }

  if(typeof o.$test !== 'function'){
    o.$test = util.test.$equal;
  }

  if(typeof o.$match !== 'function'){
    o.$match = (o.$count && util.match.$count) || util.match.$equal;
  }

  --length;
  o.$result = o.$count ? 0 : [];
  var index = -1, store = this.store;
  // ^ better than index < length-1 in `whilst`

  o.$count = 0;
  (function whilst(){
    o.$acc = true;
    var doc = store[++index];
    var match = (spec || Object.keys(doc)).filter(function(key){
      return (o.$acc = o.$test(fields, doc, key, o));
    }).length;

    if(match){
      ++o.$count;
      o.$match(doc, o);
      if(o.$break){ return ; }
    }
    if(index < length){ whilst(); }
  })();

  if(o.$ref){ return o.$result; }
  return util.clone(o.$result, true);
};

/**
## findOne

This method is just sugar on top of `find` enforcing
it to always return an object or null if nothing was found.

```js
var foundOne = find(fields, {
  $test: [Function] // if given
  $break: true,
  $count: false
})[0];
```

### spec
```js
function findOne(fields[, options|function $test])
```
<commit_msg>refactor: simplifying find to be faster<commit_after>'use strict';

var util = require('./lib/util');

exports = module.exports = Mase;

/**
### README

The module exports a constructor.

```js
var Mase = require('mase');
var db = new Mase(string name, array data)
```

Memory dbs with the same `name` share memory.
It can take an array of `data` as a second or first argument.
If so, `data` is upserted into the db by only using each
document's `_id` as a selector.

### spec
```js
function Mase([string name, array data])
```

_arguments_
- `name` type string, name for the collection
- `data` type array to insert in memory db

_defaults_
- `name` to a random 24 length string using `[A-Za-z0-9]` characters
- `data`
  - to the previous collection `name` array if there was one
  - to empty array if collection `name` it wasn't

_returns_ a new instance

_instance properties_
- `store` array of data of the instance

**/

// dbs namespace
var mase = {};

function Mase(name, data){
  if(!(this instanceof Mase)){
    return new Mase(name, data);
  }

  data = data || name;
  this.name = util.type(name).string || util.randomID();
  if(mase[this.name]){ return mase[this.name]; }

  this.store = [];
  mase[this.name] = this;

  if(util.type(data).array){
    data.forEach(function(item){
      this.update({_id: item._id}, item, {$upsert: true});
    }, this);
  }
}

/**
## insert

Insert a new document. When `document` is not a plain object,
i.e. `'one', 1, etc.`, it will be inserted using its value.

```js
db.insert(1); // => {value: 1, _id: 'wZy7rd33Q8478pvi'}
```

### spec
```js
function insert(object|any document)
```

_arguments_
 - `document` type object or any, element to insert.

_defaults_
 - `document._id` to a 24 length random string

_returns_ a clone of the inserted `document`

**/
Mase.prototype.insert = function(_doc){
  var doc = util.clone(util.type(_doc).plainObject || {value: _doc}, true);
  if(doc._id === void 0){ doc._id = util.randomID(); }
  this.changed('insert', doc, null);
  this.store.push(doc);
  return doc;
};

/**
## find

Find documents. By default, documents are tested
for value equality with [`lodash.isEqual`][p-lodash.isEqual].
This can be changed using a `$test` function.

### spec
```js
function find([object fields, object options|function $test])
```

_arguments_
 - `fields` type object, document fields for lookup
 - `$test` type function that will test `fields` for each document
 - `options` type object, helper object on how to do the lookup

_options_ properties
 - `$acc` type boolean, return value of the `$test` function
 - `$test` type function that tests `fields` against each document
 - `$break` type boolean, wether to **break the search after a match**
 - `$count` type boolean, wether to have `result` start as `0` or `[]`
 - `$result` type array or number, result returned. It will start as:
  - empty array when `$count` is falsy
  - `0` when `$count` is truthy

`$test(fields, doc, key, options)` _arguments_
 - `fields` object fields given as argument
 - `doc` document object found
 - `key` property of `fields` to be tested
 - `options` the options object passed in to `find`

_defaults_
- `options.$test` an equality test using [`lodash.isEqual`][p-lodash.isEqual]

_returns_ `o.$result`

[p-lodash.isEqual]: https://lodash.com/docs#isEqual
**/
Mase.prototype.find = function(fields, o){
  try { var spec = typeof fields === 'object' && Object.keys(fields); }
  catch(err){
    throw new TypeError('find([fields, object|function options]) '
      + '1st argument should be function or plainObject'
    );
  }

  o = (typeof o === 'object' && o) || {$test: o || fields};
  if(typeof o.$test !== 'function'){ o.$test = util.$test; }

  var index = -1;
  var store = this.store;
  var length = store.length;

  function search(){
    var doc = store[++index];
    if(o.$test(fields, doc, spec, o)){
      o.$result.push(doc);
      if(o.$break){ return ; }
    }
    if(index < length){ search(); }
  }

  if(!length || (!spec && typeof o.$test !== 'function')){
    o.$result = this.store;
  } else {
    --length; // <- better than index < length-1 whilst search
    o.$result = []; search();
  }

  if(o.$ref){ return o.$result; }
  return util.clone(o.$result, true);
};

/**
## findOne

This method is just sugar on top of `find` enforcing
it to always return an object or null if nothing was found.

```js
var foundOne = find(fields, {
  $test: [Function] // if given
  $break: true,
  $count: false
})[0];
```

### spec
```js
function findOne(fields[, options|function $test])
```

{'fraction_non_alphanumeric': 0.15901656642397383, 'fraction_numerical': 0.0049593753297457, 'mean_word_length': 3.6234146341463416, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 5, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1383197', 'n_tokens_mistral': 3199, 'n_tokens_neox': 3112, 'n_words': 1081}