docs: function factoryObjectGraph(ParentClassGraph) {
    /**
     * Inherited class. Class with methods for control links in graph.
     * Adapted for array collection.
     * 
     * @class
     * @description `import { ObjectGraph as Graph } from 'ancient-graph';`
     */
    class ObjectGraph extends ParentClassGraph {
      
      /**
       * Construct new graph and checks for required adaptation methods.
       * @param {Array[]} collection
       * @param {Object.<string, string>} fields - matching of fields in the link with fields in document
       * @param {Object} [config] - Additional config.
       * @param {Object} [config.aliases]
       * @param {String} [config.aliases.$]
       * @throws {Error} if the adapter methods is not complete
       */
      constructor(collection, fields, config) {
        super(...arguments);
        this.emitter = new EventEmitter();
      }
      
      /**
       * Specifies the id field on insert
       * 
       * @param {number} index
       * @param {Link} link
       * @return {number|string} id;
       */
      _idGenerator(index, link) {
        return ""+index;
      };
      
      /**
       * Generate insert modifier.
       * 
       * @param {number} index
       * @param {Link} link
       * @return {number|string} id;
       */
      _insertModifier(link) {
        var _modifier = {};
        for (var f in link) {
          if (this.fields[f]) {
            _modifier[this.fields[f]] = link[this.config.aliases[f]];
          }
        }
        return _modifier;
      };
      
      /**
       * Should insert new link into graph.
       * Return a synchronous result. This can be useful in your application. But for writing generic code, it is recommended to only use the callback result.
       * 
       * @param {Link} link
       * @param {Graph~insertCallback} [callback]
       * @param {Object} [context]
       * @return {number|string} [id]
       */
      insert(link, callback, context) {
        this.callback
        var _modifier = this._insertModifier(link);
        var index, error, id;
        try {
          index = this.collection.push(_modifier) - 1;
          if (!this.collection[index].hasOwnProperty(this.fields[this.config.aliases['id']])) {
            id = this.collection[index][this.fields[this.config.aliases['id']]] = this._idGenerator(index, this.collection[index]);
          }
          this.emitter.emit('insert', this.collection[index]);
        } catch(_error) {
          error = _error;
        }
        if (callback) {
          callback(error, id)
        }
        return id;
      }
      
      /**
       * Optional callback. If present, called with an error object as the first argument and, if no error, the unique id of inserted link as the second.
       *
       * @callback Graph~insertCallback
       * @param {Error} [error]
       * @param {number|string} [id]
       */
      
      /**
       * Push into link value some item/items.
       * 
       * @param {Array} data
       * @param {string|number|string[]|number[]} item
       */
      _updateModifierPush(data, item) {
        data.push(item);
      }
      
      /**
       * Push into link value some item/items if not already exists.
       * 
       * @param {Array} data
       * @param {string|number|string[]|number[]} item
       */
      _updateModifierAdd(data, item) {
        if (lodash.isArray(item)) {
          for (var i in item) {
            this._updateModifierAdd(data, item[i]);
          }
        } else {
          var index = lodash.indexOf(data, item);
          if (index < 0) {
            this._updateModifierPush(data, item);
          }
        }
      }
       
      /**
       * Remove from link value some item/items.
       * 
       * @param {Array} data
       * @param {string|number|string[]|number[]} item
       */
      _updateModifierRemove(data, item) {
        if (lodash.isArray(item)) {
          for (var i in item) {
            this._updateModifierRemove(data, item[i]);
          }
        } else {
          lodash.remove(data, function(value) {
            return value == item;
          });
        }
      }
    
      /**
       * Generate update modifier.
       * 
       * @param {LinkModifier} modifier 
       * @param {Object} result
       * @return {number|string} id;
       */
      _updateModifier(modifier, result) {
        for (var m in modifier) {
          if (this.fields[m]) {
            if (typeof(modifier[m]) == 'undefined') {
              delete result[this.fields[m]];
            } else {
              if (typeof(modifier[m]) == 'object') {
                if (lodash.isArray(modifier[m])) {
                  result[this.fields[m]] = modifier[m];
                } else {
                  if (!lodash.isArray(result[this.fields[m]])) {
                    result[this.fields[m]] = result[this.fields[m]]?[result[this.fields[m]]]:[];
                  }
                  for (var key in modifier[m]) {
                    if (key == 'add') {
                      this._updateModifierAdd(result[this.fields[m]], modifier[m][key]);
                    }
                    if (key == 'push') {
                      this._updateModifierPush(result[this.fields[m]], modifier[m][key]);
                    }
                    if (key == 'remove') {
                      this._updateModifierRemove(result[this.fields[m]], modifier[m][key]);
                    }
                  }
                }
              } else {
                result[this.fields[m]] = modifier[m];
              }
            }
          }
        }
      };
      
      /**
       * Should update to new state of modifier object link by unique id or by link query object.
       * If the database allows, it is recommended to return a synchronous result. This can be useful in your application. But for writing generic code, it is recommended to only use the callback result.
       * 
       * @param {string|LinkSelector} selector
       * @param {LinkModifier} modifier
       * @param {Graph~updateCallback} [callback]
       * @param {Object} [context]
       * @return {number} [count]
       */
      update(selector, modifier, callback, context) {
        var results = this._fetch(selector);
        for (var r in results) {
          var oldResult = lodash.cloneDeep(results[r]);
          this._updateModifier(modifier, results[r]);
          this.emitter.emit('update', results[r], oldResult);
        }
        if (callback) callback(undefined, results.length);
        return results.length;
      }
      
      /**
       * Optional callback. If present, called with an error object as the first argument and, if no error, the number of affected documents as the second.
       *
       * @callback Graph~updateCallback
       * @param {Error} [error]
       * @param {number} [count]
       */
      
      /**
       * Should remove link by unique id or by link query object.
       * 
       * @param {string|LinkSelector} selector
       * @param {Graph~removeCallback} [callback]
       * @param {Object} [context]
       */
      remove(selector, callback, context) {
        var oldLength = this.collection.length;
        var removed = [];
        lodash.remove(this.collection, (result) => {
          var _query = this.query(selector)(result);
          if (_query) removed.push(result)
          return _query;
        });
        for (var r in removed) {
          this.emitter.emit('remove', removed[r]);
        }
        var newLength = this.collection.length;
        if (callback) callback(undefined, oldLength - newLength);
      }
      
      /**
       * Optional callback. If present, called with an error object as the first argument.
       *
       * @callback Graph~removeCallback
       * @param {Error} [error]
       * @param {number} [count]
       */
      
      /**
       * Generate adapter for database query for links search by unique id or by link query object.
       * 
       * @param {string|LinkSelector} selector
       * @return {*} query
       */
      query(selector) {
        var type = typeof(selector);
        if (type == 'string' || type == 'number') {
          return (doc) => { return doc[this.fields[this.config.aliases['id']]] == selector };
        } else if (type == 'object') {
          return (doc) => {
            if (typeof(doc) != 'object') return false;
            for (var m in selector) {
              if (this.fields[m]) {
                if (typeof(selector[m]) == 'undefined') {
                  if (doc.hasOwnProperty(this.fields[m])) return false;
                } else {
                  if (lodash.isArray(doc[this.fields[m]])) {
                    if (lodash.isArray(selector[m])) {
                      for (var s in selector[m]) {
                        if (!lodash.includes(doc[this.fields[m]], selector[m][s])) {
                          return false;
                        }
                      }
                    } else {
                      if (!lodash.includes(doc[this.fields[m]], selector[m])) {
                        return false;
                      }
                    }
                  } else {
                    if (doc[this.fields[m]] != selector[m]) return false;
                  }
                }
              }
            }
            return true;
          }
        }
      }
      
      /**
       * Generate adapted for database options object.
       * 
       * @param {Object} [options]
       * @return {*} options - a options suitable for the database
       */
      options(options) {
        var _options = {};
        if (options) {
          if (options.sort) {
            _options.sort = { keys: [], orders: [] };
            for (var s in options.sort) {
              if (this.fields[s]) {
                _options.sort.keys.push(this.fields[s]);
                _options.sort.orders.push(options.sort[s]?'asc':'desc');
              }
            }
          }
          if (typeof(options.skip) == 'number') {
            _options.skip = options.skip;
          }
          if (typeof(options.limit) == 'number') {
            _options.limit = options.limit;
          }
        }
        return _options;
      }
      
      /**
       * Generate Link from document by fields.
       * 
       * @param {Object} document
       * @return {Link} link
       */
      _generateLink(document) {
        var link = {};
        for (var f in this.fields) {
          if (document.hasOwnProperty(this.fields[f])) {
            link[f] = document[this.fields[f]];
          }
        }
        return link;
      }
      
      /**
       * Get one first matching link.
       * 
       * @param {string|LinkSelector} selector
       * @param {SelectOptions} [options]
       * @param {Graph~getCallback} [callback]
       * @return {Link} link - result link object
       */
      get(selector, options, callback) {
        var results = this.fetch(selector, options, (error, results) => {
          if (callback) callback(error, results?results[0]:undefined);
        });
        if (results) return results[0];
      }
      
      /**
       * Fetch native database documents.
       * 
       * @param {string|linkSelector} selector
       * @param {SelectOptions} [options]
       * @return {Object[]} documents - result documents
       */
      _fetch(selector, options) {
        var query = this.query(selector);
        var documents = lodash.filter(this.collection, query); 
        var _options = this.options(options);
        if (_options.sort) documents = lodash.orderBy(documents, _options.sort.keys, _options.orders);
        var skip = _options.skip?_options.skip:0;
        var limit = _options.limit?skip+_options.limit:_options.limit;
        documents = documents.slice(skip, limit);
        return documents;
      }
      
      /**
       * Find and all matching links as an Array.
       * 
       * @param {string|LinkSelector} selector
       * @param {SelectOptions} [options]
       * @param {Graph~fetchCallback} [callback]
       * @return {Link[]} links - result links objects in array
       */
      fetch(selector, options, callback) {
        var documents = this._fetch(selector, options);
        var links = [];
        for (var d in documents) {
          links.push(this._generateLink(documents[d]));
        }
        if (callback) callback(undefined, links);
        return links;
      }
      
      /**
       * Optional callback. If present, called with an error object as the first argument and, if no error, the result links objects in array.
       *
       * @callback Graph~fetchCallback
       * @param {Error} [error]
       * @param {Link[]} [links]
       */
      
      /**
       * Should call callback once for each matching document, sequentially and synchronously.
       * 
       * @param {string|LinkSelector} selector
       * @param {SelectOptions} [options]
       * @param {Graph~eachCallback} [callback]
       */
      each(selector, options, callback) {
        var links = this.fetch(selector, options);
        for (var l in links) {
          callback(links[l]);
        }
      }
      
      /**
       * @callback Graph~eachCallback
       * @param {Link} [link]
       */
      
      /**
       * Map callback over all matching documents. Returns an Array.
       * 
       * @param {string|LinkSelector} selector
       * @param {SelectOptions} [options]
       * @param {Graph~mapCallback} [callback]
       * @return {Array} results
       */
      map(selector, options, callback) {
        var links = this.fetch(selector, options);
        return links.map(callback);
      }
      
      /**
       * @callback Graph~mapCallback
       * @param {Link} [link]
       * @return {*} result
       */
      
      /**
       * Count all matching documents.
       * 
       * @param {string|LinkSelector} selector
       * @param {SelectOptions} [options]
       * @param {Graph~countCallback} [callback]
       * @return {number} [count]
       */
      count(selector, options, callback) {
        var links = this.fetch(selector, options);
        if (callback) callback(undefined, links.length);
        return links.length;
      }
      
      /**
       * @callback Graph~countCallback
       * @param {Error} [error]
       * @param {number} [count]
       */
      
      /**
       * Should subscribe to the events: link, unlink, insert, update, remove.
       * 
       * @param {string} event - One event name
       * @param {Graph~onCallback} callback
       * @returns {Function} Stops event subscription.
       * @example
       * var counter = 0;
       * var stop = graph.on('update', (oldData, newData) => {
       *   if (oldData.id == '1') console.log(oldData.id, 'is changed');
       *   counter++;
       *   if (counter == 3) stop();
       * });
       */
      on(event, callback) {
        var subscriptions = [];
        
        if (event == 'insert') {
          subscriptions.push(this.emitter.addListener('insert', (document) => {
            callback(undefined, this._generateLink(document));
          }));
        }
        if (event == 'update') {
          subscriptions.push(this.emitter.addListener('update', (newDocument, oldDocument) => {
            callback(this._generateLink(oldDocument), this._generateLink(newDocument));
          }));
        }
        if (event == 'remove') {
          subscriptions.push(this.emitter.addListener('remove', (document) => {
            callback(this._generateLink(document), undefined);
          }));
        }
        if (event == 'link') {
          subscriptions.push(this.emitter.addListener('insert', (document) => {
            callback(undefined, this._generateLink(document));
          }));
          subscriptions.push(this.emitter.addListener('update', (newDocument, oldDocument) => {
            callback(this._generateLink(oldDocument), this._generateLink(newDocument));
          }));
        }
        if (event == 'unlink') {
          subscriptions.push(this.emitter.addListener('update', (newDocument, oldDocument) => {
            callback(this._generateLink(oldDocument), this._generateLink(newDocument));
          }));
          subscriptions.push(this.emitter.addListener('remove', (document) => {
            callback(this._generateLink(document), undefined);
          }));
        }
        
        return () => {
          for (var subscription of subscriptions) {
            subscription.remove();
          }
        };
      }
      
      /**
       * @callback Graph~onCallback
       * @param {Link} [oldLink] - can be undefined on link and insert events
       * @param {Link} [newLink] - can be undefined on unlink and remove events
       * @param {Object} [context] - additional app information, such as context.userId
       */
    }
    
    return ObjectGraph;
  }