Home

Awesome

Installation

Install with npm: npm install quiver-firebase-search.

Configuration

Configure Firebase

Configure env.json

Configure Elasticsearch

Configure Algolia

Testing

Example Usage

var FirebaseSearch = require('firebase-search.js');
var firebase = require('firebase');

firebase.initializeApp({
  "databaseURL": "https://quiver-firebase-search.firebaseio.com",
  "serviceAccount": "./service-account.json"
});

var usersRef = firebase.database().ref('demo/users');
var elasticsearchConfig = {
    host: 'localhost:9200',
    log: 'warning',
    index: 'development'
  };
var algoliaConfig =  {
  "applicationID": "XXXXXXXX",
  "searchAPIKey": "XXXXXXXX",
  "monitoringAPIKey": "XXXXXXXX",
  "apiKey": "XXXXXXXX"
};
 
var search = new FirebaseSearch(usersRef, {
  elasticsearch: elasticsearchConfig,
  algolia: algoliaConfig
}, 'users');

// Optional elasticsearch configuration settings
var config = {
  added: { /* settings passed into elasticsearch client create function */ },
  changed: { /* settings passed into elasticsearch client update function  */ },
  deleted: { /* settings passed into elasticsearch client delete function  */ }
};

search.elasticsearch.firebase.start(config);
search.algolia.firebase.start();

FirebaseSearch Functions

FirebaseSearch.elasticsearch.client

The entire Elasticsearch client api is available via FirebaseSearch.elasticsearch.client.

FirebaseSearch.prototype.elasticsearch

A number of top-level Elasticsearch functions are proxied by FirebaseSearch from the original api. They're used internally by FirebaseSearch and also exist to provide a nice Promise-based api.

All functions assumed the default index and type values, although they can be overridden as needed. So where you'd typically need to make a call like firebasSearch.elasticsearch.create({index: 'development', type: 'users', body: {name: 'Chris'}});, with the proxied version you can simply call firebasSearch.elasticsearch.create({body: {name: 'Chris'}});.

Elasticsearch top-level proxied functions

FirebaseSearch.prototype.elasticsearch.ping()

search.elasticsearch.ping()
  .then(function (isThisOn) {
    console.log('Is this thing on?', isThisOn);
  });

FirebaseSearch.prototype.elasticsearch.create(requestObject)

search.elasticsearch.create({
  body: {
    name: 'Chris'
  }
})
  .then(function (res) {
    console.log('Create response', res);
  });

FirebaseSearch.prototype.elasticsearch.update(requestObject)

search.elasticsearch.update({
  body: {
    doc: {
      name: 'Spike'
    }
  }
})
  .then(function (res) {
    console.log('Update response', res);
  });

FirebaseSearch.prototype.elasticsearch.delete(requestObject)

search.elasticsearch.delete({
  id: 'someUserId'
})
  .then(function (res) {
    console.log('Delete response', res);
  });

FirebaseSearch.prototype.elasticsearch.exists(requestObject)

search.elasticsearch.exists({
  id: 'someUserId'
})
  .then(function (exists) {
    console.log('Does this record exist?', exists);
  });

FirebaseSearch.prototype.elasticsearch.get(requestObject)

search.elasticsearch.get({
  id: 'someUserId'
})
  .then(function (res) {
    console.log('Get response', res);
  });

FirebaseSearch.prototype.elasticsearch.search(requestObject)

search.elasticsearch.search({
  q: 'name:Chris'
})
  .then(function (res) {
    console.log('Search response', res);
  });

FirebaseSearch.prototype.elasticsearch.indices

The functions found under FirebaseSearch.prototype.elasticsearch.indices are all proxies of their corresponding Elasticsearch functions. The only difference is that you don't have to specify any parameters to use them, because FirebaseSearch already knows which index you're using and defaults to that index. Of course, you can always override the default parameters if necessary.

Elasticsearch proxied index functions

Usage

search.elasticsearch.indices.exists()
  .then(function (exists) {
    console.log('Does the index exist?', exists);
  });

search.elasticsearch.indices.delete()
  .then(function () {
    console.log('index deleted');
  });

search.elasticsearch.indices.create()
  .then(function () {
    console.log('index created');
  });

search.elasticsearch.indices.ensure()
  .then(function () {
    console.log('index created if necessary');
  });

FirebaseSearch.prototype.elasticsearch.firebase

The functions found under FirebaseSearch.prototype.elasticsearch.firebase handle common Firebase operations.

FirebaseSearch.prototype.elasticsearch.firebase.build()

Builds the Elasticsearch index to reflect all existing Firebase records

search.elasticsearch.firebase.build()
  .then(function () {
    console.log('Index built and synced with current Firebase state.');
  })

FirebaseSearch.prototype.elasticsearch.firebase.start()

Starts listening to Firebase records additions, changes and removals, syncing Elasticsearch appropriately

Optional config is used to pass custom parameters to ElasticSearch's create, update, and delete function calls.

// Optional elasticsearch configuration settings
var config = {
  added: { /* settings passed into elasticsearch client create function */ },
  changed: { /* settings passed into elasticsearch client update function  */ },
  deleted: { /* settings passed into elasticsearch client delete function  */ }
};

search.elasticsearch.firebase.start(config)
  .then(function () {
    console.log('Syncing Elasticsearch with Firebase');
  })

FirebaseSearch.prototype.elasticsearch.firebase.stop()

Stops listening to Firebase and syncing Elasticsearch

search.elasticsearch.firebase.stop()
  .then(function () {
    console.log('Stopped syncing Elasticsearch with Firebase');
  })

FirebaseSearch.algolia.client

Provides access to the Algolia client api

FirebaseSearch.algolia.index

Provides access to the Algolia index api

FirebaseSearch.prototype.algolia

These proxied functions are used internally by FirebaseSearch and are also available for manipulating Algolia.

These functions all return promises and can be called so that they wait for Algolia to finish its operations and confirm success before resolving the promise. Algolia returns all write operations immediately and provides a waitTask(taskID) function to wait for task completion.

FirebaseSearch.prototype.algolia.search(searchText, options)

Takes a search string as a first argument and an optional search options objects as a second argument.

search.algolia.search('search text', {
  hitsPerPage: 25
})
  .then(function (res) {
    console.log('Search results', res);
  });

FirebaseSearch.prototype.algolia.addObject(object, shouldWait)

search.algolia.addObject({
  name: 'Chris',
  objectID: '123456'
}, true)
  .then(function (res) {
    console.log('Object added', res);
  });

FirebaseSearch.prototype.algolia.saveObject(object, shouldWait)

search.algolia.saveObject({
  name: 'Chris',
  objectID: '123456'
}, true)
  .then(function (res) {
    console.log('Object saved', res);
  });

FirebaseSearch.prototype.algolia.deleteObject(objectID, shouldWait)

search.algolia.deleteObject('123456', true)
  .then(function (res) {
    console.log('Object deleted', res);
  });

FirebaseSearch.prototype.algolia.setSettings(settings)

search.algolia.setSettings({
  customRanking: ['desc(height)']
})
  .then(function () {
    console.log('Setting set');
  });

FirebaseSearch.prototype.algolia.listIndexes()

search.algolia.listIndexes()
  .then(function (indexes) {
    console.log('indexes', indexes);
  });

FirebaseSearch.prototype.algolia.clearIndex()

search.algolia.clearIndex()
  .then(function () {
    console.log('Index cleared');
  });

FirebaseSearch.prototype.algolia.waitTask()

search.algolia.index.partialUpdateObject({
  objectID: '123456',
  favoriteColor: 'green'
}, function (err, content) {
  search.algolia.waitTask(content.taskID)
    .then(function () {
      console.log('task complete');
    });
});

###FirebaseSearch.prototype.algolia.exists(objectType)

Algolia doesn't come with an "exists" function out of the box. But Elasticsearch's exist function is so useful, we might as well pre-package one for Algolia as well.

search.algolia.exists('users')
  .then(function (exists) {
    console.log('Users index exists', exists);
  });

FirebaseSearch.prototype.algolia.firebase

The functions found under FirebaseSearch.prototype.algolia.firebase handle common Firebase operations.

FirebaseSearch.prototype.algolia.firebase.build()

Builds the Algolia index to reflect all existing Firebase records

search.algolia.firebase.build()
  .then(function () {
    console.log('Index built and synced with current Firebase state.');
  })

FirebaseSearch.prototype.algolia.firebase.start()

Starts listening to Firebase records additions, changes and removals, syncing Algolia appropriately

search.algolia.firebase.start()
  .then(function () {
    console.log('Syncing Algolia with Firebase');
  })

FirebaseSearch.prototype.algolia.firebase.stop()

Stops listening to Firebase and syncing Algolia

search.algolia.firebase.stop()
  .then(function () {
    console.log('Stopped syncing Algolia with Firebase');
  })

Events

Syncing with Elasticsearch and Algolia is all so asynchronous and difficult to track, that an events system is the easiest way to manage wait for syncing operations.

These events are all called after syncing has been completed by one of the *.start functions.

The all event is mostly for debugging, but it could be used for all sorts of stuff. It's fired every time any other event is fired.

Usage

search.on('all', function (e){
  console.log('Event name', e.name);
  console.log('Event detail', e.detail);
});

search.on('elasticsearch_child_added', function (record){
  console.log('Record synced', record);
});

search.on('elasticsearch_child_changed', function (record){
  console.log('Record synced', record);
});

search.on('elasticsearch_child_removed', function (record){
  console.log('Record synced', record);
});

search.on('algolia_child_added', function (record){
  console.log('Record synced', record);
});

search.on('algolia_child_changed', function (record){
  console.log('Record synced', record);
});

search.on('algolia_child_removed', function (record){
  console.log('Record synced', record);
});