atomic-algolia
Advanced tools
Comparing version 0.2.1 to 0.3.0
@@ -0,14 +1,14 @@ | ||
var actionAdd = require("./utils/actionAdd") | ||
var actionUpdate = require("./utils/actionUpdate") | ||
var actionDelete = require("./utils/actionDelete") | ||
var algoliaSearch = require("algoliasearch") | ||
var addAction = require("./utils/addAction") | ||
var chalk = require("chalk") | ||
var deleteAction = require("./utils/deleteAction") | ||
var dotenv = require("dotenv").config() | ||
var fs = require("fs") | ||
var idsFromIndex = require("./utils/idsFromIndex") | ||
var idsFromQuery = require("./utils/idsFromQuery") | ||
var getLocalIndex = require("./utils/getLocalIndex") | ||
var getRemoteIndex = require("./utils/getRemoteIndex") | ||
var calculateOperations = require("./utils/calculateOperations") | ||
var path = require("path") | ||
var title = chalk`[{blue Algolia}]` | ||
module.exports = function update(indexName, indexFile, cb) { | ||
module.exports = function update(indexName, indexData, cb) { | ||
try { | ||
@@ -18,7 +18,4 @@ if (!indexName) | ||
if (!indexFile) | ||
throw new Error("Please provide `indexFile`") | ||
if (!fs.existsSync(indexFile)) | ||
throw new Error("Index file `" + indexFile + "` not found") | ||
if (!indexData) | ||
throw new Error("Please provide `indexData`. A valid Javacript object or path to a JSON file.") | ||
@@ -32,45 +29,54 @@ var client = algoliaSearch( | ||
var index = client.initIndex(indexName) | ||
var newIndex = JSON.parse(fs.readFileSync(path.resolve(indexFile), "utf-8")) | ||
var newIndexIds = idsFromIndex(newIndex) | ||
var newIndex = getLocalIndex(indexData) | ||
idsFromQuery(index, function(err, res) { | ||
if (err) throw err | ||
return getRemoteIndex(index) | ||
.then(function(oldIndex) { | ||
// Figure out which records to add or delete | ||
var operations = calculateOperations(newIndex, oldIndex) | ||
// Figure out which records to add or delete | ||
var operations = calculateOperations(newIndexIds, res) | ||
if (verbose === true) { | ||
console.log(title, `Adding ${operations.add.length} hits to ${indexName}`) | ||
onsole.log(title, `Updating ${operations.update.length} hits to ${indexName}`) | ||
console.log(title, `Removing ${operations.delete.length} hits from ${indexName}`) | ||
console.log(title, `${operations.ignore.length} hits unchanged in ${indexName}`) | ||
} | ||
if (verbose === true) { | ||
console.log(title, `Adding ${operations.add.length} hits to ${indexName}`) | ||
console.log(title, `Removing ${operations.delete.length} hits from ${indexName}`) | ||
console.log(title, `${operations.add.length} hits unchanged in ${indexName}`) | ||
} | ||
// Fetch full records from operation ids | ||
var toAddRecords = newIndex.filter(function(hit) { | ||
return operations.add.indexOf(hit.objectID) !== -1 | ||
}) | ||
// Fetch full records from operations.add ids | ||
var toAddRecords = newIndex.filter(function(hit) { | ||
return operations.add.indexOf(hit.objectID) !== -1 | ||
}) | ||
var toUpdateRecords = newIndex.filter(function(hit) { | ||
return operations.update.indexOf(hit.objectID) !== -1 | ||
}) | ||
// Create batch update actions | ||
var toAdd = toAddRecords.map(function(record) { | ||
return addAction(record, indexName) | ||
}) | ||
// Create batch update actions | ||
var toAdd = toAddRecords.map(function(record) { | ||
return actionAdd(record, indexName) | ||
}) | ||
var toDelete = operations.delete.map(function(id) { | ||
var action = deleteAction(id, indexName) | ||
return action | ||
}) | ||
var batchActions = [].concat(toAdd, toDelete) | ||
var toUpdate = toUpdateRecords.map(function(record) { | ||
return actionUpdate(record, indexName) | ||
}) | ||
// Perform the batch API call | ||
if (batchActions.length > 0) { | ||
// Notify client this is coming from this script | ||
client.setExtraHeader("X-FORWARDED-BY", "HUGO-ALGOLIA") | ||
client.batch(batchActions, function(err, res) { | ||
if (err) throw err | ||
var toDelete = operations.delete.map(function(id) { | ||
return actionDelete(id, indexName) | ||
}) | ||
var batchActions = [].concat(toAdd, toUpdate, toDelete) | ||
cb(null, res) | ||
}) | ||
} | ||
}) | ||
// Perform the batch API call | ||
if (batchActions.length > 0) { | ||
// Notify client this is coming from this script | ||
client.setExtraHeader("X-FORWARDED-BY", "HUGO-ALGOLIA") | ||
client.batch(batchActions, function(err, res) { | ||
if (err) throw err | ||
cb(null, res) | ||
}) | ||
} | ||
}) | ||
.catch(function(err) { | ||
cb(err) | ||
}) | ||
} catch (err) { | ||
@@ -77,0 +83,0 @@ cb(err) |
@@ -1,19 +0,83 @@ | ||
module.exports = function calculateOperations(newIndexIds, oldIndexIds) { | ||
var operations = {unchanged: [], add: newIndexIds, delete: oldIndexIds} | ||
var idsFromIndex = require("./idsFromIndex") | ||
var md5 = require("md5") | ||
module.exports = function calculateOperations(newIndex, oldIndex) { | ||
var newIndexIds = idsFromIndex(newIndex) | ||
var oldIndexIds = idsFromIndex(oldIndex) | ||
var existingHits = [] | ||
var operations = {ignore: [], update: [], add: newIndexIds, delete: oldIndexIds} | ||
if (newIndexIds.length > 0 && oldIndexIds.length > 0) { | ||
operations.unchanged = newIndexIds.filter(function(hit) { | ||
return oldIndexIds.indexOf(hit) !== -1 | ||
}) | ||
existingHits = findExistingHits(newIndexIds, oldIndexIds) | ||
operations.add = findNewHits(newIndexIds, oldIndexIds) | ||
operations.delete = findExpiredHits(newIndexIds, oldIndexIds) | ||
} | ||
operations.add = newIndexIds.filter(function(hit) { | ||
return oldIndexIds.indexOf(hit) === -1 | ||
}) | ||
if (existingHits.length > 0) { | ||
operations.ignore = findUnchangedHits(existingHits, newIndex, oldIndex) | ||
operations.update = findChangedHits(existingHits, newIndex, oldIndex) | ||
} | ||
operations.delete = oldIndexIds.filter(function(hit) { | ||
return newIndexIds.indexOf(hit) === -1 | ||
}) | ||
console.log(operations) | ||
return operations | ||
} | ||
function findNewHits(newIndexIds, oldIndexIds) { | ||
return newIndexIds.filter(function(hit) { | ||
return oldIndexIds.indexOf(hit) === -1 | ||
}) | ||
} | ||
function findExpiredHits(newIndexIds, oldIndexIds) { | ||
return oldIndexIds.filter(function(hit) { | ||
return newIndexIds.indexOf(hit) === -1 | ||
}) | ||
} | ||
function findExistingHits(newIndexIds, oldIndexIds) { | ||
return newIndexIds.filter(function(hit) { | ||
return oldIndexIds.indexOf(hit) !== -1 | ||
}) | ||
} | ||
function findUnchangedHits(existingHits, newIndex, oldIndex) { | ||
return existingHits.filter(function(id) { | ||
var shouldUpdate = compareHitFromIndexes(id, newIndex, oldIndex) | ||
if (shouldUpdate !== true) { | ||
return id | ||
} | ||
}) | ||
} | ||
function findChangedHits(existingHits, newIndex, oldIndex) { | ||
return existingHits.filter(function(id) { | ||
var shouldUpdate = compareHitFromIndexes(id, newIndex, oldIndex) | ||
if (shouldUpdate === true) { | ||
return id | ||
} | ||
}) | ||
} | ||
function compareHitFromIndexes(id, newIndex, oldIndex) { | ||
var newHit = newIndex.filter(function(hit) { | ||
return hit.objectID === id | ||
}) | ||
var oldHit = oldIndex.filter(function(hit) { | ||
return hit.objectID === id | ||
}) | ||
if (newHit.length > 0 && oldHit.length > 0 ) { | ||
var newHitSorted = JSON.stringify(newHit, Object.keys(newHit).sort()) | ||
var oldHitSorted = JSON.stringify(oldHit, Object.keys(oldHit).sort()) | ||
var newHash = md5(newHitSorted) | ||
var oldHash = md5(oldHitSorted) | ||
return newHash !== oldHash | ||
} | ||
return operations | ||
return null | ||
} |
@@ -1,11 +0,11 @@ | ||
module.exports = function idsFromIndex(newIndex) { | ||
return newIndex.reduce(function(hits, hit) { | ||
if (hit !== null && hit !== undefined) { | ||
return hits.concat(hit) | ||
} | ||
}, []).reduce(function(hits, hit) { | ||
if (hit.objectID !== null && hit.objectID !== undefined) { | ||
return hits.concat(hit.objectID) | ||
module.exports = function idsFromIndex(index) { | ||
return index.reduce(function(hits, hit) { | ||
if (hit !== null && hit !== undefined) { | ||
return hits.concat(hit) | ||
} | ||
}, []) | ||
} | ||
}, []).reduce(function(hits, hit) { | ||
if (hit.objectID !== null && hit.objectID !== undefined) { | ||
return hits.concat(hit.objectID) | ||
} | ||
}, []) | ||
} |
{ | ||
"name": "atomic-algolia", | ||
"version": "0.2.1", | ||
"description": "An NPM script for running atomic updates to an Algolia index with a local JSON file", | ||
"version": "0.3.0", | ||
"description": "An NPM package for running atomic updates to an Algolia index", | ||
"main": "lib/index.js", | ||
@@ -13,3 +13,11 @@ "scripts": { | ||
], | ||
"author": "@chrisdmacrae", | ||
"engines": { | ||
"node": ">=7.6.0" | ||
}, | ||
"repository": "github:chrisdmacrae/atomic-algolia", | ||
"author": { | ||
"name": "Chris D. Macrae", | ||
"email": "hello@chrisdmacrae.com", | ||
"url": "chrisdmacrae.com" | ||
}, | ||
"license": "ISC", | ||
@@ -19,4 +27,5 @@ "dependencies": { | ||
"chalk": "^2.3.0", | ||
"dotenv": "^5.0.0" | ||
"dotenv": "^5.0.0", | ||
"md5": "^2.2.1" | ||
} | ||
} |
@@ -5,6 +5,10 @@ # Algolia Atomic | ||
## How it works | ||
This package runs *atomic* updates to an Algolia Index from a local JSON file. **What does that mean?** | ||
This package runs *atomic* updates to an Algolia Index. **What does that mean?** | ||
Simply put, this package reads a local JSON file, and updates the *new* or *updated* records, while removing deleted records. It does this *all at once*, so your index is never out of sync, and you use the smallest amount of operations possible. *(Stay on that free plan as long as you can!)* | ||
Simply put, this package reads your local index, and updates the *new* or *updated* records, while removing deleted records. | ||
It does this *all at once*, so your index is never out of sync with your content, and you use the smallest amount of operations possible. | ||
*(Stay on that free plan as long as you can!)* | ||
## Installation | ||
@@ -20,3 +24,3 @@ To install this script, you must have [Node & NPM installed](https://nodejs.org/en/download/). Once installed, run the following command in your terminal: | ||
It reads a local JSON file with an array of valid records. For example: | ||
It reads an array of objects or local JSON file with an array of valid records. For example: | ||
@@ -35,3 +39,3 @@ ``` | ||
> Note, this package can only be used in NPM scripts to update a single index. To update multiple indices, create your own script by following the instructions in [Javascript Files](#javascript-files) | ||
> Note, this package can only be used in NPM scripts to update a single index from a local JSON file. To update multiple indices or pass in a Javascript object, create your own script by following the instructions in [Javascript Files](#javascript-files) | ||
@@ -58,2 +62,4 @@ | ||
#### Using with a local JSON file | ||
``` | ||
@@ -78,3 +84,31 @@ var atomicalgolia = require("atomic-algolia") | ||
#### Using with an Array of Objects | ||
``` | ||
var atomicalgolia = require("atomic-algolia") | ||
var indexName = "example_index" | ||
var indexData = [ | ||
{ | ||
objectID: "1", | ||
title: "An example record" | ||
} | ||
] | ||
var cb = function(error, result) { | ||
if (err) throw error | ||
console.log(result) | ||
} | ||
atomicalgolia(indexName, indexPath, cb) | ||
``` | ||
Then call the script from your terminal as follows: | ||
``` | ||
ALGOLIA_APP_ID={{ YOUR_APP_ID}} ALGOLIA_ADMIN_ID={{ YOUR_ADMIN_ID }} YOUR_SCRIPT.js | ||
``` | ||
## Using a `.env` file | ||
@@ -81,0 +115,0 @@ A `.env` file can be added to the root of your project with the required environment variables. This way, you don't have to specify them in `package.json` or when running the command. |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
16994
12
226
130
4
10
1
+ Addedmd5@^2.2.1
+ Addedcharenc@0.0.2(transitive)
+ Addedcrypt@0.0.2(transitive)
+ Addedis-buffer@1.1.6(transitive)
+ Addedmd5@2.3.0(transitive)