pouchdb-collate
Advanced tools
Comparing version 1.1.2 to 1.2.0
{ | ||
"name": "pouchdb-collate", | ||
"version": "0.2.0", | ||
"version": "1.2.0", | ||
"homepage": "https://github.com/pouchdb/collate", | ||
@@ -5,0 +5,0 @@ "authors": [ |
{ | ||
"name": "pouchdb-collate", | ||
"version": "0.2.0", | ||
"version": "1.2.0", | ||
"repo": "pouchdb/collate", | ||
@@ -5,0 +5,0 @@ "authors": [ |
@@ -123,2 +123,134 @@ !function(e){"object"==typeof exports?module.exports=e():"function"==typeof define&&define.amd?define(e):"undefined"!=typeof window?window.pouchCollate=e():"undefined"!=typeof global?global.pouchCollate=e():"undefined"!=typeof self&&(self.pouchCollate=e())}(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){ | ||
function parseNumber(str, i) { | ||
var originalIdx = i; | ||
var num; | ||
var zero = str[i] === '1'; | ||
if (zero) { | ||
num = 0; | ||
i++; | ||
} else { | ||
var neg = str[i] === '0'; | ||
i++; | ||
var numAsString = ''; | ||
var magAsString = str.substring(i, i + MAGNITUDE_DIGITS); | ||
var magnitude = parseInt(magAsString, 10) + MIN_MAGNITUDE; | ||
if (neg) { | ||
magnitude = -magnitude; | ||
} | ||
i += MAGNITUDE_DIGITS; | ||
while (true) { | ||
var ch = str[i]; | ||
if (ch === '\u0000') { | ||
break; | ||
} else { | ||
numAsString += ch; | ||
} | ||
i++; | ||
} | ||
numAsString = numAsString.split('.'); | ||
if (numAsString.length === 1) { | ||
num = parseInt(numAsString, 10); | ||
} else { | ||
num = parseFloat(numAsString[0] + '.' + numAsString[1]); | ||
} | ||
if (neg) { | ||
num = num - 10; | ||
} | ||
if (magnitude !== 0) { | ||
// parseFloat is more reliable than pow due to rounding errors | ||
// e.g. Number.MAX_VALUE would return Infinity if we did | ||
// num * Math.pow(10, magnitude); | ||
num = parseFloat(num + 'e' + magnitude); | ||
} | ||
} | ||
return {num: num, length : i - originalIdx}; | ||
} | ||
// move up the stack while parsing | ||
// this function moved outside of parseIndexableString for performance | ||
function pop(stack, metaStack) { | ||
var obj = stack.pop(); | ||
if (metaStack.length) { | ||
var lastMetaElement = metaStack[metaStack.length - 1]; | ||
if (obj === lastMetaElement.element) { | ||
// popping a meta-element, e.g. an object whose value is another object | ||
metaStack.pop(); | ||
lastMetaElement = metaStack[metaStack.length - 1]; | ||
} | ||
var element = lastMetaElement.element; | ||
var lastElementIndex = lastMetaElement.index; | ||
if (Array.isArray(element)) { | ||
element.push(obj); | ||
} else if (lastElementIndex === stack.length - 2) { // obj with key+value | ||
var key = stack.pop(); | ||
element[key] = obj; | ||
} else { | ||
stack.push(obj); // obj with key only | ||
} | ||
} | ||
} | ||
exports.parseIndexableString = function (str) { | ||
var stack = []; | ||
var metaStack = []; // stack for arrays and objects | ||
var i = 0; | ||
while (true) { | ||
var collationIndex = str[i++]; | ||
if (collationIndex === '\u0000') { | ||
if (stack.length === 1) { | ||
return stack.pop(); | ||
} else { | ||
pop(stack, metaStack); | ||
continue; | ||
} | ||
} | ||
switch (collationIndex) { | ||
case '1': | ||
stack.push(null); | ||
break; | ||
case '2': | ||
stack.push(str[i] === '1'); | ||
i++; | ||
break; | ||
case '3': | ||
var parsedNum = parseNumber(str, i); | ||
stack.push(parsedNum.num); | ||
i += parsedNum.length; | ||
break; | ||
case '4': | ||
var parsedStr = ''; | ||
while (true) { | ||
var ch = str[i]; | ||
if (ch === '\u0000') { | ||
break; | ||
} | ||
parsedStr += ch; | ||
i++; | ||
} | ||
// perform the reverse of the order-preserving replacement | ||
// algorithm (see above) | ||
parsedStr = parsedStr.replace(/\u0001\u0001/g, '\u0000') | ||
.replace(/\u0001\u0002/g, '\u0001') | ||
.replace(/\u0002\u0002/g, '\u0002'); | ||
stack.push(parsedStr); | ||
break; | ||
case '5': | ||
var arrayElement = { element: [], index: stack.length }; | ||
stack.push(arrayElement.element); | ||
metaStack.push(arrayElement); | ||
break; | ||
case '6': | ||
var objElement = { element: {}, index: stack.length }; | ||
stack.push(objElement.element); | ||
metaStack.push(objElement); | ||
break; | ||
default: | ||
throw new Error( | ||
'bad collationIndex or unexpectedly reached end of input: ' + collationIndex); | ||
} | ||
} | ||
}; | ||
function arrayCollate(a, b) { | ||
@@ -125,0 +257,0 @@ var len = Math.min(a.length, b.length); |
132
lib/index.js
@@ -122,2 +122,134 @@ 'use strict'; | ||
function parseNumber(str, i) { | ||
var originalIdx = i; | ||
var num; | ||
var zero = str[i] === '1'; | ||
if (zero) { | ||
num = 0; | ||
i++; | ||
} else { | ||
var neg = str[i] === '0'; | ||
i++; | ||
var numAsString = ''; | ||
var magAsString = str.substring(i, i + MAGNITUDE_DIGITS); | ||
var magnitude = parseInt(magAsString, 10) + MIN_MAGNITUDE; | ||
if (neg) { | ||
magnitude = -magnitude; | ||
} | ||
i += MAGNITUDE_DIGITS; | ||
while (true) { | ||
var ch = str[i]; | ||
if (ch === '\u0000') { | ||
break; | ||
} else { | ||
numAsString += ch; | ||
} | ||
i++; | ||
} | ||
numAsString = numAsString.split('.'); | ||
if (numAsString.length === 1) { | ||
num = parseInt(numAsString, 10); | ||
} else { | ||
num = parseFloat(numAsString[0] + '.' + numAsString[1]); | ||
} | ||
if (neg) { | ||
num = num - 10; | ||
} | ||
if (magnitude !== 0) { | ||
// parseFloat is more reliable than pow due to rounding errors | ||
// e.g. Number.MAX_VALUE would return Infinity if we did | ||
// num * Math.pow(10, magnitude); | ||
num = parseFloat(num + 'e' + magnitude); | ||
} | ||
} | ||
return {num: num, length : i - originalIdx}; | ||
} | ||
// move up the stack while parsing | ||
// this function moved outside of parseIndexableString for performance | ||
function pop(stack, metaStack) { | ||
var obj = stack.pop(); | ||
if (metaStack.length) { | ||
var lastMetaElement = metaStack[metaStack.length - 1]; | ||
if (obj === lastMetaElement.element) { | ||
// popping a meta-element, e.g. an object whose value is another object | ||
metaStack.pop(); | ||
lastMetaElement = metaStack[metaStack.length - 1]; | ||
} | ||
var element = lastMetaElement.element; | ||
var lastElementIndex = lastMetaElement.index; | ||
if (Array.isArray(element)) { | ||
element.push(obj); | ||
} else if (lastElementIndex === stack.length - 2) { // obj with key+value | ||
var key = stack.pop(); | ||
element[key] = obj; | ||
} else { | ||
stack.push(obj); // obj with key only | ||
} | ||
} | ||
} | ||
exports.parseIndexableString = function (str) { | ||
var stack = []; | ||
var metaStack = []; // stack for arrays and objects | ||
var i = 0; | ||
while (true) { | ||
var collationIndex = str[i++]; | ||
if (collationIndex === '\u0000') { | ||
if (stack.length === 1) { | ||
return stack.pop(); | ||
} else { | ||
pop(stack, metaStack); | ||
continue; | ||
} | ||
} | ||
switch (collationIndex) { | ||
case '1': | ||
stack.push(null); | ||
break; | ||
case '2': | ||
stack.push(str[i] === '1'); | ||
i++; | ||
break; | ||
case '3': | ||
var parsedNum = parseNumber(str, i); | ||
stack.push(parsedNum.num); | ||
i += parsedNum.length; | ||
break; | ||
case '4': | ||
var parsedStr = ''; | ||
while (true) { | ||
var ch = str[i]; | ||
if (ch === '\u0000') { | ||
break; | ||
} | ||
parsedStr += ch; | ||
i++; | ||
} | ||
// perform the reverse of the order-preserving replacement | ||
// algorithm (see above) | ||
parsedStr = parsedStr.replace(/\u0001\u0001/g, '\u0000') | ||
.replace(/\u0001\u0002/g, '\u0001') | ||
.replace(/\u0002\u0002/g, '\u0002'); | ||
stack.push(parsedStr); | ||
break; | ||
case '5': | ||
var arrayElement = { element: [], index: stack.length }; | ||
stack.push(arrayElement.element); | ||
metaStack.push(arrayElement); | ||
break; | ||
case '6': | ||
var objElement = { element: {}, index: stack.length }; | ||
stack.push(objElement.element); | ||
metaStack.push(objElement); | ||
break; | ||
default: | ||
throw new Error( | ||
'bad collationIndex or unexpectedly reached end of input: ' + collationIndex); | ||
} | ||
} | ||
}; | ||
function arrayCollate(a, b) { | ||
@@ -124,0 +256,0 @@ var len = Math.min(a.length, b.length); |
{ | ||
"name": "pouchdb-collate", | ||
"version": "1.1.2", | ||
"version": "1.2.0", | ||
"description": "", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
PouchDB Collate | ||
=== | ||
Collation functions for mapreduce and search plugins, give it 2 things, it returns a number. | ||
Collation functions for PouchDB map/reduce. Used by PouchDB map/reduce to maintain consistent [CouchDB collation ordering](https://wiki.apache.org/couchdb/View_collation). | ||
The PouchDB Collate API is not exposed by PouchDB itself, but if you'd like to use it in your own projects, it's pretty small, and it has a few functions you may find useful. | ||
Usage | ||
----- | ||
In Node: | ||
``` | ||
$ npm install pouchdb-collate | ||
``` | ||
``` | ||
var pouchCollate = require('pouchdb-collate'); | ||
``` | ||
In the browser you can install with Bower: | ||
``` | ||
$ bower install pouchdb-collate | ||
``` | ||
Or just download from the [releases page](https://github.com/pouchdb/collate/releases). | ||
Then it will be available as `window.pouchCollate`. | ||
API | ||
---- | ||
### toIndexableString(obj) | ||
This is probably the most useful function in PouchDB Collate. It converts any object to a serialized string that maintains proper CouchDB collation ordering in both PouchDB and CouchDB (ignoring some subtleties with ICU string ordering in CouchDB vs. ASCII string ordering in PouchDB). | ||
So for example, if you want to sort your documents by many properties in an array, you can do e.g.: | ||
```js | ||
var pouchCollate = require('pouchdb-collate'); | ||
var myDoc = { | ||
firstName: 'Scrooge', | ||
lastName: 'McDuck', | ||
age: 67, | ||
male: true | ||
}; | ||
// sort by age, then gender, then last name, then first name | ||
myDoc._id = pouchCollate.toIndexableString( | ||
[myDoc.age, myDoc.male, mydoc.lastName, mydoc.firstName]); | ||
``` | ||
The doc ID will be: | ||
```js | ||
'5323256.70000000000000017764\u000021\u00004McDuck\u00004Scrooge\u0000\u0000' | ||
``` | ||
Which is of course totally not human-readable, but it'll sort everything correctly (floats, booleans, ints – you name it). If you need a human-readable doc ID, check out the [DocURI](https://github.com/jo/docuri) project. | ||
### collate(obj1, obj2) | ||
Give it two objects, and it'll return a number comparing them. For example: | ||
```js | ||
pouchCollate.collate('foo', 'bar'); // 1 | ||
pouchCollate.collate('bar', 'foo'); // -1 | ||
pouchCollate.collate('foo', 'foo'); // 0 | ||
``` | ||
Of course it sorts more than just strings - any valid JavaScript object is sortable. | ||
### normalizeKey(obj) | ||
You shouldn't need to use this, but this function will normalize the object and return what CouchDB would expect - e.g. `undefined` becomes `null`, and `Date`s become `date.toJSON()`. It's basically what you would get if you called: | ||
```js | ||
JSON.parse(JSON.stringify(obj)); | ||
``` | ||
but a bit faster. |
103
test/test.js
@@ -8,2 +8,3 @@ 'use strict'; | ||
var toIndexableString = pouchCollate.toIndexableString; | ||
var parseIndexableString = pouchCollate.parseIndexableString; | ||
var utils = require('../lib/utils'); | ||
@@ -317,2 +318,104 @@ | ||
}); | ||
it('verify parseIndexableString', function () { | ||
var keys = [null, false, true, 0, 1, -1, 9, -9, 10, -10, 0.1, -0.1, -0.01, | ||
100, 200, 20, -20, -200, -30, Number.MAX_VALUE, Number.MIN_VALUE, | ||
'foo', '', '\u0000', '\u0001', '\u0002', [1], {foo: true}, | ||
{foo: 'bar', 'baz': 'quux', foobaz: {bar: 'bar', baz: 'baz', quux: {foo: 'bar'}}}, | ||
{foo: {bar: true}}, | ||
[{foo: 'bar'}, {bar: 'baz'}, {}, ['foo', 'bar', 'baz']], | ||
[[[['foo']], [], [['bar']]]], | ||
-Number.MAX_VALUE, | ||
-300, | ||
-200, | ||
-100, | ||
-10, | ||
-2.5, | ||
-2, | ||
-1.5, | ||
-1, | ||
-0.5, | ||
-0.0001, | ||
-Number.MIN_VALUE, | ||
0, | ||
Number.MIN_VALUE, | ||
0.0001, | ||
0.1, | ||
0.5, | ||
1, | ||
1.5, | ||
2, | ||
3, | ||
10, | ||
15, | ||
100, | ||
200, | ||
300, | ||
Number.MAX_VALUE, | ||
'', | ||
'1', | ||
'10', | ||
'100', | ||
'2', | ||
'20', | ||
'[]', | ||
//'é', | ||
'foo', | ||
'mo', | ||
'moe', | ||
//'moé', | ||
//'moët et chandon', | ||
'moz', | ||
'mozilla', | ||
'mozilla with a super long string see how far it can go', | ||
'mozzy', | ||
[], | ||
[ null ], | ||
[ null, null ], | ||
[ null, 'foo' ], | ||
[ false ], | ||
[ false, 100 ], | ||
[ true ], | ||
[ true, 100 ], | ||
[ 0 ], | ||
[ 0, null ], | ||
[ 0, 1 ], | ||
[ 0, '' ], | ||
[ 0, 'foo' ], | ||
[ '', '' ], | ||
[ 'foo' ], | ||
[ 'foo', 1 ], | ||
{}, | ||
{ '0': null }, | ||
{ '0': false }, | ||
{ '0': true }, | ||
{ '0': 0 }, | ||
{ '0': 1 }, | ||
{ '0': 'bar' }, | ||
{ '0': 'foo' }, | ||
{ '0': 'foo', '1': false }, | ||
{ '0': 'foo', '1': true }, | ||
{ '0': 'foo', '1': 0 }, | ||
{ '0': 'foo', '1': '0' }, | ||
{ '0': 'foo', '1': 'bar' }, | ||
{ '0': 'quux' }, | ||
{ '1': 'foo' } | ||
]; | ||
keys.forEach(function (key) { | ||
var indexableString = toIndexableString(key); | ||
JSON.stringify(parseIndexableString(indexableString)).should.equal( | ||
JSON.stringify(key), 'check parseIndexableString for key: ' + key + | ||
'(indexable string is: ' + indexableString + ')'); | ||
}); | ||
}); | ||
it('throws error in parseIndexableString on invalid input', function () { | ||
try { | ||
var obj = parseIndexableString(''); | ||
should.fail("didn't expect to parse correctly"); | ||
} catch (err) { | ||
should.exist(err); | ||
} | ||
}); | ||
}); |
Sorry, the diff of this file is not supported yet
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
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
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
72587
1197
81
0