esobject

1.0.0-beta.105 • Public • Published

ESObject

Bring your Elasticsearch documents to life!

This library now supports Elastisearch 6. It will keep backward compatibility with ES 5 as long as possible. Probably up to Elasticsearch 9.

ESObject is an Object Document Mapper (ODM) library to interface your Elasticsearch documents with the real world. Tranformation, validation, protection and many more features made possible through highly customizable strategies.

Pangolin


Installation

You can install this package with NPM :

npm install --save esobject

Documentation conventions

Most of the ESObject methods (or generated methods) return a thenable promise. Throughout this documentation we may act as if it is a common and well-known fact.

Very basic usage

(Let's say we are working with a zoo full of cute little animals.)

var elasticsearch = require('elasticsearch');
var esobject = require('esobject');

// --- Create your model
var Animal = esobject.create({
  name: 'Animal', // lowercase name is used on elasticsearch
});

// --- Create an index
var index = new esobject.Index(new elasticsearch.Client(), 'app-index');
index.add(Animal);

// --- Get an existing instance
index.get(Animal, 'id')
  .then(function(animal) {
    // --- (and do something with it)
  })
;

// --- Create a new instance
var myAnimal = new Animal(index, {
  species: 'Rockhopper Penguin',
  name: 'Etienne',
});

// --- and save it
myAnimal.save()
  .then(function() {
    console.log('This animal has been saved into Elasticsearch');
  })
;
// or
index.save(myAnimal)
  .then(function() {
    console.log('This animal has been saved into Elasticsearch');
  })
;

Topics


1 Creating a model

Creating a model is done through the esobject#create(options) method. Possible options are described below. Keep reading!


1.1 Creating a model - Mapping

When creating a new model you may define its Elasticsearch mapping (more information about Elasticsearch mapping here). This mapping can then be applied with the Index#createOrUpdate method.

Mapping can be defined as :

  • a javascript object
  • a javascript or YAML file (js-yaml package will need to be installed separately)
/*  ---- Mapping example - as an object ---- */
var elasticsearch = require('elasticsearch');
var esobject = require('esobject');

var Animal = esobject.create({
  name: 'Animal',

  strategies: {
    dynamic: 'strict',
    properties: {
      species: 'string',
      name: 'string',
      surname: { type: 'string', index: 'not_analyzed' },
      created: { type: 'date', format: 'date_time' },
    },
  },
});

var index = new esobject.Index(new elasticsearch.Client, 'app-index');
index.add(Animal),

// --- Synchronize mapping with Elasticsearch
index.createOrUpdate({/* index settings */})
  .then(function() {
    console.log('Index has been created/updated');
  })
;

As you can see, the mapping resides in the strategies property. This convention is on purpose so you know the format is not exactly the one elasticseach accepts.

It is extended in a lot of ways that are going to be explained further on but, as of now, you can notice that you don't have to pass a {type: 'typename'} object for each property if the type is the only thing you want to specify ('typename' is enough).

/*  ---- Mapping example - as a YAML file ---- */
var Animal = esobject.create({
  name: 'Animal',
  strategies: __dirname + '/animal_mapping.yaml',
});

Here is an example for animal_mapping.yaml:

dynamic: strict
properties:
  species: string
  name: string
  surname:
    type: string
    index: not_analyzed
  created:
    type: date
    format: date_time

You can require a sub file from your YAML whenever you want using the !require tag:

dynamic: strict
properties:
  species: string
  name: string
  surname:
    type: string
    index: not_analyzed
  created:
    type: date
    format: date_time
  # this will set the root of location.yaml in the location property
  location: !require
    file: location.yaml # path is relative to this file (eg ./ is implicit)
  # this will set the city.name property of address.yaml in the city property
  city: !require
    file: address.yaml
    prop: city.name
  # you can also load JS files using !require
  location: !require
    file: super.js
    # you can also use prop here

1.2 Creating a model - Queries

Named queries can be defined on your model. They can be used at the model level (statics), or on a particular instance (methods).

Queries can be defined as:

  • a function
  • < other formats pending >

On a method query, you can access the current instance through this (see the examples below).

  • statics queries can be called with Model#nameOfYourQuery(options)
  • methods queries can be called with instance.nameOfYourQuery(options)

The query methods can be injected with a number of parameters (the name of the argument will be used to inject its value like angular does):

  • index: the index on which the operations are performed (either options.index if provided or the internal object of your instance if it exists (only on methods))
  • AnyType: if an index is present, any type in it can be injected
  • options: the options object you provided to the query
/*  ---- Queries examples ---- */
var esobject = require('esobject');

var Animal = esobject.create({
  // --- Static queries, available on the model
  statics: {
    searchByName: function(index, Animal, options) {
      return index.search(Animal, {query: {term: {name: options.name}}});
    },
  },
  // --- Method queries, available on an instance
  methods: {
    // -- the current instance is available in this
    searchHomonyms: function(index, Animal) {
      return index.search(Animal, {
        query: {term: {name: this.name}},
        filter: {not: {term: {_id: this._id}}},
      });
    },
  },
});

Animal.searchByName({name: 'Etienne', index: someIndex})
  .then(console.log.bind(console.log))
;

// -- If you have an Animal instance (thisAnimal)
thisAnimal.searchHomonyms({index: someIndex})
  .then(function(result) {
    console.log('There are ' + result.total + ' animals named like this one.');
  })
;

1.3 Creating a model - Import and Export

When creating a new model, you can define import and export strategies to interface your documents.

The import strategy is used to define how data from the real world will be imported into your documents; the default strategy can be applied with instance.import(rawData) or with instance.importDefault(rawData).

The export strategy let you decide how data from your documents will be exposed in your instance; the default strategy can be applied with instance.export() or with instance.exportDefault().

Both import and export methods will return a promise. import will resolve on the modified instance while export will resolve on a raw javascript object.

Strategies are objects that will link fields of the document to different behaviours described using functions.

For all behaviours, this function will allow you to inject multiple things :

  • The index if present, looked for in options and then in the model instance if possible (index)
  • The options if provided (options)
  • All arguments provided to the import / export function, except the raw data for import (args)

For each of those, you can inject only sub properties by using $ to denote the path to your property (eg options$sub$property will inject options.res.property in this variable).

If a function returns a promise, the resolved value will be used for the operation. If the (resolved) value is undefined then the property will be deleted from the resulting object.


1.3.1 Import behaviours

For import behaviours, you will also be able to inject :

  • The value already present before the strategy is applied (old)
  • The value of the equivalent field in the raw data being imported (raw)
  • The value in the instance being transformed (res) / you should use this with extreme care

For old, raw and res, since you may be on a sub property of the root object, you can also reference the parent property using $parent (and its parent using $parent$parent and so on) and the root object using $root like in : raw$parent$name that will inject in this variable the name property that is a sibling to the current one in the imported raw object.

By default, import behaviours import nothing. In other words, you have to explicitely state every property you want to import and how it should be imported.

/* Strategy - import as a function - example */
var esobject = require('esobject');

var Animal = esobject.create({
  name: 'Animal',
  strategies: {
    properties: {
      name: {
        type: 'string',

        $import: {
          // -- In this example, a new value will be imported in name only if it starts with the same character as the value already present before the import
          default: (old, raw) => raw && old[0] === raw[0] ? raw : old,
        }
      },
    },
  },
});

myAnimal = new Animal({name: 'Mike'});

myAnimal.import({name: 'André', species: 'Chameleon'})
  .then(function(myAnimal) {
    // --- At this point :
    // --- myAnimal.name == 'Mike';
  })
;

myAnimal.import({name: 'Madonna', species: 'Chameleon'})
  .then(function(myAnimal) {
    // --- At this point :
    // --- myAnimal.name == 'Madonna';
  })
;

1.3.2 Export behaviours

For an export behaviour, the function will allow you to inject:

  • The value in the instance before export (obj)
  • The value in the resulting object during export (res) / you should use this with extreme care

Like for import, obj & res support the $parent & $root notations.

By default, export behaviours exports everything. In other words, you have to explicitely prevent private fields from being exported (by putting there a function returning undefined).

/* Strategy - export as a function - example */
var ESObject = require('esobject');

var Animal = ESObject.create({
  name: 'Animal',
  strategies: {
    properties: {
      timid: 'boolean',
      name: {
        type: 'string',

        $export: {
          // -- In this example, we will expose the name only if the animal is not a timid one
          default: (obj, obj$root$timid) => !obj$root$timid ? obj : undefined,
        },
      },
    },
  },
});

thisAnimal = new Animal({
  name: 'Roger',
  timid: true,
});

myAnimal.export()
  .then(function(exportedAnimal) {
    // --- At this point :
    // --- 'name' in exportedAnimal == false;

    myAnimal.timid = false;
    return myAnimal.export();
  })
  .then(function(exportedAnimal) {
    // --- At this point :
    // --- exportedAnimal.name == 'Roger';
  })
;

1.3.3 Additional arguments

For both import and export behaviours, you can easily manipulate additional arguments:

/* Strategy - export as a function, additional parameters - example */
var esobject = require('esobject');

var Animal = ESObject.create({
  name: 'Animal',
  strategies: {
    properties: {
      name: {
        $export: {
          default: args => args[0] == 'the first argument of export',
        },
        $import: {
          default: args => args[0] == 'the second argument on import',
        },
      },
    },
  },
});

var myAnimal = new Animal();

myAnimal.import(rawData, param1)
  .then(function(myAnimal) {
    // -- Do something clever
  })
;

myAnimal.export(param1, param2)
  .then(function(myAnimal) {
    // -- Do something clever
  })
;

1.3.4 Named import & export behaviours

You can also define named behaviors alongside the default import and export behaviours. They will be accessible using the importName() and exportName() methods on your model instances:

var Animal = ESObject.create({
  name: 'Animal',
  strategies: {
    properties: {
      name: {
        $import: {
          name: raw => raw || '<no name>',
        },
      },
    },
  },
});

var myAnimal = new Animal();

myAnimal.importName({})
  .then(function(myAnimal) {
    // --- At this point :
    // --- myAnimal.name == '<no name>';
  })
;

1.3.5 Import & export strategies helpers

ESObject exports a strategies object containing two properties import and export. Each contains tools to help you creating common basic strategies.

  • esobject.strategies.import.id([keepOld]): copy over the value of raw in the resulting object, if keepOld is true (defaults to false), if raw is undefined, it tries to keep old
  • esobject.strategies.import.default([strategy, ] defaultValue): applies the provided strategy first (it can be another helper) and if it resolves to undefined
  • esobject.strategies.import.copy(path [, root]): copy the imported property (in res) using the value provided as path in raw. By default, path starts from the parent object of the current property. If root is given true, path starts from the root object.
  • esobject.strategies.export.copy(path [, root]): create the exported property by copying over the value provided as path. By default, path starts from the parent object of the current property. If root is given true, path starts from the root object.
  • esobject.strategies.export.drop(): create a strategy that drops the provided field from the export (basically a function that returns undefined)
  • < more to come… >

See the examples below :

/* Strategy - helper object - import example */
var esobject = require('esobject');

var strategies = esobject.strategies;

var Animal = esobject.create({
  name: 'Animal',
  strategies: {
    properties: {
      species: {
        type: 'string',
        $import: {
          default: strategies.import.id(),
        },
      },
      subspecies: {
        type: 'string',
        $import: {
          default: strategies.import.id(),
        },
      },
      name: {
        type: 'string',
        $import: {
          default: strategies.import.id(true),
        },
      },
      surname: {
        type: 'string',
        $import: {
          default: stategies.import.default(strategies.import.id(true), 'Kiki'),
        },
      },
      age: {
        type: 'integer',
        $import: {
          default: stategies.import.copy('informations.age'),
        },
      },
    },
  },
});

var myAnimal = new Animal({
  species: 'Turtle',
  subspecies: 'Sea Turtle',
  name: 'Felipe',
  surname: 'Fefe',
});

myAnimal.import({species: 'Lizard', informations: {age: 154}})
  .then(function(myAnimal) {
    // --- At this point :
    // --- myAnimal.species == 'Lizard';
    // --- 'subspecies' in myAnimal == false;
    // --- myAnimal.name == 'Felipe';
    // --- myAnimal.surname == 'Kiki';
    // --- myAnimal.age = 154;
  })
;
/* Strategy - helper object - export example */
var esobject = require('esobject');

var Animal = esobject.create({
  name: 'Animal',
  strategies: {
    properties: {
      age: {
        type: 'string',
        $export: {
          default: strategies.export.copy('informations.age'),
        },
      },
      informations: {
        type: 'object',
        $export: {
          default: strategies.export.drop(),
        },
      },
    },
  },
});

var myAnimal = newAnimal({
  species: 'Spider',
  informations: { age: 32, size: 12 },
});

myAnimal.export()
  .then(function(myAnimal) {
    // --- At this point :
    // --- myAnimal.species = 'Spider'
    // --- myAnimal.age == 32;
    // --- 'informations' in myAnimal == false
  })
;

1.3.6 Array support

Elasticsearch supports array of values in any property given that the values respect the type of the base property. esobject is aware of such a fact and applies import and export behaviours on all elements of the array if there is one.

To achieve this, esobject autodetects the presence of an array in the raw data during import or in the exported object during export. You can force esobject to expect an array or a plain value and to fail if the correct one is not there by specifying $array: [true/false] in your property description.

/* Strategy - helper object - $all example */
var esobject = require('esobject');

var strategies = esobject.strategies;

function createNephews($array) {
  var nephews = {
    type: 'object',

    properties: {
      name: {
        type: 'string',
        import: {
          default: strategies.import.id(),
        },
      },
      age: {
        type: 'integer',
        import: {
          default: strategies.import.id(),
        },
      },
    },
  };

  if ($array === false || $array === true)
    nephews.$array = $array;

  return nephews;
}

var Animal = esobject.create({
  name: 'Animal',
  properties: {
    nephews: createNephews(),
    nephewsArray: createNephews(true),
    nephew: createNephews(false),
  },
});

var myAnimal = new Animal();

myAnimal.import({
  nephews: [
    {name: 'Louie', age: 14},
    {name: 'Huey', age: 14},
    {name: 'Dewey'},
  ],
})
  .then(function(myAnimal) {
    // --- At this point :
    // --- myAnimal.nephews == [{name: 'Louie', age: 14}, {name: 'Huey', age: 14}, {name: 'Dewey'}]
  })
;

myAnimal.import({
  nephews: {name: 'Louie', age: 14},
})
  .then(function(myAnimal) {
    // --- At this point :
    // --- myAnimal.nephews == {name: 'Louie', age: 14}
  })
;

// this will return a failed promise since a plain object is provided
myAnimal.import({nephewsArray: {name: 'Louie', age: 14}});

// this will return a failed promise since an array is provided
myAnimal.import({nephew: [{name: 'Louie', age: 14}]});

1.3.7 $check function during import

All object properties, including the root object, can contain a $check property. It can contain a function or an object mapping names to functions. If it contains a function, it will behave as if you wrote $check: {default: theFunction}.

After all the behaviours of the current object have been applied, the $check function corresponding to your current import (if existant) will be called. This allows you to easily perform security checks on the changes that just happened.

$check functions can be injected in the same way than import functions.

/* Strategies - $check example */
var esobject = require('esobject');

var Animal = esobject.create({
  name: 'Animal',

  strategies: {
    $check: {
      // since we apply this check on default import, we could have
      // added this function on $check directly
      default: function(old, res, options) {
        if (res.name === undefined)
          throw new Error('This animal has no name !!!');

        if (old.species !== res.species && !options.admin)
          throw new Error('Only admins can change the species property');
      },
    },

    properties: {
      name: /* some behaviour */,
      surname: /* some behaviour */,
      species: /* some behaviour */,
    },
  },
});

var index = // get an index

index.get(Animal, 'some_animal')
  .then(myAnimal => myAnimal.import({/* some data */}, {admin: false}))
  .then(function(myAnimal) { /* Do something clever */ })
  .catch(function(err) { /* Do something clever */ })
;

2 Store

A store is a collection of types that works together. They are useful to describe the minimum types required to achieve a functionality for example.

2.1 Creating a store

It is very easy to create a store. Just use the new operator on its constructor exposed by the ESObject library. The constructor has the following signature: Store(storesOrTypes...).

/* Strategies - creating a store */
var esobject = require('esobject');

var Type1 = esobject.create({name: 'Type1'});
var Type2 = esobject.create({name: 'Type2'});
var Type3 = esobject.create({name: 'Type3'});

// Creates an empty store
var store = new esobject.Store();
// Creates a store with a single type in it
store = new esobject.Store(Type1);
// Creates a store with multiple types in it (you can pass more of them)
store = new esobject.Store(Type2);

// Creates a store with a store in it and types (you can pass more stores too)
var store2 = new esobject.Store(store, Type3);

2.2 Store methods - add

A store is composed of one or more types that it will manipulate. add() allows you to add stores or types in it. You can pass any number of them to the add method.

// All tose are valid add calls
store.add(Type1);
store.add(Type2, Type3);
store.add(store1);
store.add(store2, store3);
store.add(store4, Type4, store5);

2.3 Store methods - remove

A store is composed of one or more types that it will manipulate. remove() allows you to remove types or stores from itself. You can pass any number of them to the remove method.

// All tose are valid add calls
store.remove(Type1);
store.remove(Type2, Type3);
store.remove(store1);
store.remove(store2, store3);
store.remove(store4, Type4, store5);

2.4 Store methods - getType

The store.getType(typeName) method takes a string argument and returns the type having this name in this store (or one of its substores). It throws if no type of the provided name was found.

// Get the Type1 type from store
var Type1 = store.getType('Type1');
// Throws
store.getType('ThisTypeIsNotInThisStore');

2.5 Store methods - getTypes

The store.getTypes() method returns an object mapping type names to actual types for every type in it and its substores.

var Type1 = store.getTypes().Type1;

3 Index

Index are stores and this contain every methods of stores. Thus an index can be provided wherever a store is required. Indexes though are linked to a specific elasticsearch client (and thus to a specific cluster) and an index in it.

Be careful though: it is not a very bright idea to pass an index to the store.add method since it would negate its use since stores are not aware of elasticsearch clients. If you want to do that, you probably should create an intermediary store.

var functionStore = new esobject.Store(/* functionStore types */);

// -- bad way of doing this
var index = new esobject.Index(/* args */);
index.add(/* index types */)

functionStore.add(index); // this is valid but is probably a mistake

// -- looking correct
var indexTypesStore = new esobject.Store(/* index types */);
functionStore.add(indexTypesStore);

var index = new esobject.Index(/* args */);
index.add(indexTypesStore);

3.1 Creating an index

An index is created using the new operator on the esobject.Index(client, indexName[, Types...] contructor.

It takes first the elasticsearch client that the index should use to perform its operations and secondly the name of the index to use on the elasticseach cluster.

You can then pass any number of arguments. They will all be forwarded to the index.add() method.


3.2 Index methods - search

Search queries can be executed with index.search(Types..., query[, params]). Query definition and returned values are the same as named queries. See the Creating a model - Queries section for more information.

You can optionnally add an object containing any parameters accepted by the elsaticsearch.js client#search method. (See here for more information about those).

/*  ---- Search query example ---- */
var esobject = require('esobject');

var Animal = esobject.create({name: 'Animal'});

var index = new esobject.Index(esClient, 'app-index');

// Single type search
index.search(Animal, {query: {term: {name: 'Etienne' }}}, {analyzer: 'french'})
  .then(function(results) {
    console.log(results); // array of results
  })
;

// Search for multiple types in the same index in a single query
index.search(Animal, Owner, Country, query);

Search returns an Array of results. It is extended with the following properties:

  • results.total: number of matches in the cluster
  • results.aggregations: aggregations returned by your query
  • results.export[Name](): functions to export every item in the results
  • results.import[Name](): functions to import raw data in every item in the results

3.3 Index methods - get

Given its elasticsearch id, you can retrieve an instance with index.get(Type, id[, params]).

You can optionnally add an object containing any parameters accepted by the elsaticsearch.js client#get method. (See here for more information about those).

/*  ---- Model#get example ---- */
var esobject = require('esobject');

var Animal = esobject.create({name: 'Animal'});

var index = // create an index

index.get(Animal, 'id')
  .then(function(myAnimal) {
    // -- Do something with it
  })
;

// -- With elsaticsearch.js client#get options
Animal.get(Animal, 'id', {parent: 'parent_id'})
  .then(function(myAnimal) {
    // -- Do something with it
  })
;

3.4 Index methods - save

As implied, this method saves a model instance into elasticsearch. It looks like index.save(modelInstance[, params]).

It is safe by default, meaning it will use the _create endpoint if the object does not contain a version and otherwise send over the version.

You can optionnally add an object containing any parameters accepted by the elsaticsearch.js client#create or client#index methods. (See here and here for more information about those).

It also accepts a special params.force parameter that, if set to true, will disable the safe mode (it will never use the _create endpoint and not pass the version over to elasticseach).

/*  ---- index.save example ---- */
var esobject = require('esobject');

var Animal = esobject.create({name: 'Animal'});

var index = // create an index

var myAnimal = new Animal({name: 'Zoe'});

index.save(myAnimal)
  .then(function(myAnimal) {
    // -- At this point, those are now available:
    // myAnimal._id
    // myAnimal._version (== 1)
  })
;

// later on…

// -- With elsaticsearch.js client#[create/index] options
myAnimal = new Animal({
  _id: 'big_zoe',
  name: 'Big Zoe',
});
index.save(myAnimal, {routing: 'some_id'})
  .then(function(myAnimal) {
    // -- At this point:
    // myAnimal._id == 'big_zoe'
    // myAnimal._version == 1

    return index.save(myAnimal, {routing: 'some_id'});
  })
  .then(function(myAnimal) {
    // -- At this point:
    // myAnimal._version == 2
  })
;

// later on…

// --- Force overwrite
myAnimal = new Animal({
  _id: 'big_zoe',
  name: 'Big Zoe _The Ultimate_',
});
index.save(myAnimal, {routing: 'some_id', force: true})
  .then(function(myAnimal) {
    // -- At this point:
    // myAnimal._id == 'big_zoe'
    // myAnimal._version == 3
  })
;

Note: esobject is aware of your instance's parent (that is accessible through instance._parent) except if you prevented it from retrieving the parent through some parameters (or other means). save() will automatically forward the parent in the request so you should probably never set this parameter. Prefer the routing parameter for most use cases.


3.5 Index methods - delete

Delete an instance of a model from the index. Its signature is: index.delete(modelInstance[, params]).

It is safe by default, meaning it will send the version over to elasticseach for checking.

You can optionnally add an object containing any parameters accepted by the elsaticsearch.js client#delete method. (See here and here for more information about those).

It also accepts a special params.force parameter that, if set to true, will disable the safe mode (it will never use the _create endpoint and not pass the version over to elasticseach).

/*  ---- index.save example ---- */
var esobject = require('esobject');

var Animal = esobject.create({name: 'Animal'});

var index = // create an index

var myAnimal = new Animal({_id: 'zoe', _version: 2, name: 'Zoe'});

index.delete(myAnimal)
  .then(function(myAnimal) {
    // -- At this point, those are now available:
    // myAnimal._id
    // myAnimal._version (== 3)
  })
;

Note: esobject is aware of your instance's parent (that is accessible through instance._parent) except if you prevented it from retrieving the parent through some parameters per example. delete() will automatically forward the parent in the request so you should probably never set this parameter. Prefer the routing parameter for most use cases.


3.6 Index methods - createOrUpdate

Try to create or update the index and its type mappings. index.createOrUpdate(settings).

It computes the mapping for every type and try to create the index with the provided settings. If the index already exists, it will try to update its settings and the mapping of each type.

We recommend that you call this method before starting your app to ensure your mapping is compliant with the index you want to work with.

/*  ---- index.createOrUpdate example ---- */
var esobject = require('esobject');

var Models = // [lots of esobject models]

var index = new esobject.Index(esClient, 'app-index');
index.add.apply(index, Models); // add all types in the index

index.createOrUpdate()
  .then(function() {
    // if you are there, it means it worked

    // do something clever like starting your server
  })
;

4 MultiIndex

MultiIndex is an index aggregator. It allows to perform requests on multiple indexes at once. It does not inherits from esobject.Store nor from esobject.Index.

4.1 Creating a MultiIndex

The constructor of MultiIndex looks like MultiIndex([index...]). It creates a multi index and pass to its multiindex.add() method all the provided arguments.

var esobject = require('esobject');

var index1 = new esobject.Index(/* args */);
var index2 = new esobject.Index(/* args */);

var multiIndex = new esobject.MultiIndex();
multiIndex = new esobject.MultiIndex(index1, index2);

4.2 MultiIndex methods - add

The multiIndex.add([index...]) method adds the provided indexes to the multiindex. It does not accepts Store instances, only Index instances.

// All those are valid
multiIndex.add(); // though this is useless
multiIndex.add(index1);
multiIndex.add(index1, index2);

4.3 MultiIndex methods - remove

The multiIndex.add([index...]) method removes the provided indexes from the multiindex.

// All those are valid
multiIndex.remove(); // though this is useless
multiIndex.remove(index1);
multiIndex.remove(index1, index2);

4.4 MultiIndex methods - getType

See store.getType().


4.5 MultiIndex methods - getTypes

Aggregate results of all sub indexes types. See store.getTypes() for more information.


4.6 MultiIndex methods - search

Search for the provided query on all subindexes. See index.search() for more information.


5 Instances

5.1 Creating an instance

You can create an instance by using the new operator on an existing model. It accepts either raw answers from the elasticseach client or a data object that may or may not contain _id, _version & _parent properties.

It also accepts the index linked to the instance as an optional first argument.

/*  ---- Creating an instance - example ---- */
var esobject = require('esobject');

var Animal = esobject.create({name: 'Animal'});
var index = new esobject.Index(/* args */);

// -- Creating a new Animal with no data
var myAnimal = new Animal();
var myAnimal2 = new Animal(index);

// -- Creating a new Animal with id = 1 and version = 0
var thisAnimal = new Animal({_id: '1', _version: 0});
var thisAnimal2 = new Animal(index, {_id: '1', _version: 0});

5.2 Instance fields

On a given instance the following fields are available (they are not enumerable and will not be exported by default) :

  • _index: index the document has been loaded from
  • _type: type the document has been loaded from
  • _parent: parent document of this one (if it exists)
  • _id: id of the document this instance is referring to
  • _version: version of the document
  • _fields: object containing the named fields returned by your ES request

5.3 Instance methods - save

instance.save([params]) is an alias to index.save(instance[, params]). It will throw if the instance has no internal index.


5.4 Instance methods - delete

instance.delete([params]) is an alias to index.delete(instance[, params]). It will throw if the instance has no internal index.

Migration from ES 5 to ES 6

ESObject supports both versions without you having to care how this works. But migrating from one to the other might require some work though.

How type are resolved?

Type information was provided up to ES 5 in the _type property of the data returned by ES.

Since this field is irrevelent in the single type mode introduced by ES 5.6 & generalized in ES 6 for all new indices, the type information is now stored in a $type property of the new join type in the object's source.

The mapping looks like:

properties:
  $type:
    type: 'join'
    relations:
      # all parent/child relations are now described here automatically by index.getMappings()
  # … (all the type mappings are now merged by esobject into a single big mapping)

Testing single type mode on ES 5.6

You can use the single type mode in ES 5.6 to test out how this will work out & prep your migration by setting the singleTypeMode propetry to true on your index:

const index = new esobject.Index(esClient, 'some-index');
index.singleTypeMode = true;

You will also need to create your index with the index.mapping.single_type set to true. This can be achived using the createOrUpdate method of your index object:

index.createOrUpdate({mapping: {single_type: true}});

Important to note: Since, akwardly, ES 5 does not support typename starting with an _ in its name, esobject can't use the recommended _doc typename (in the ES 6+ documentation) for ES 5 indices and will fallback on doc instead.

Migrating index from older index to 6+ format

You can do this either on the 5.x branch, starting from the 5.6 version by reindexing your data using the new single type mode to generate your mapping. You can use your app codebase to create a new index with the correct mapping & settings applied using the instructions provided in the previous section. I would rather recommend you use this method only to proof work the migration but to use the other possibility to migrate:

You can also first upgrade your cluster to ES 6 since it supports old 5.6 indexes (this will be the last version that does). The advantage to use this method is that ES 6 does support _doc as a typename thus allowing to migrate data from the ES 5 index to the ES 6 one using the recommended names. This should ensure a smooth transition to ES 7 later on. Once this is done, create a new index with the new mapping for ES 6.

Then, in both cases, use the provided upgrading script to reindex your data into a new index:

es-upgrade-index 5to6 http://host:9200/myindex

The script will:

  • create a new index named <myindex>-v6 on the provided host, this index will have a new mapping corresponding to the new v6 format
  • backup your data from your index myindex into a file using elasticdump (the file will be named index-<myindex>-backup.dump)
  • convert the backed data in v6 format (in a file named index-<myindex>-v6.dump)
  • load the converted data in the <myindex>-v6 index

Package Sidebar

Install

npm i esobject

Weekly Downloads

98

Version

1.0.0-beta.105

License

BSD-3-Clause

Unpacked Size

138 kB

Total Files

41

Last publish

Collaborators

  • even