Collation functions for PouchDB map/reduce to maintain consistent CouchDB collation ordering
npx @tessl/cli install tessl/npm-pouchdb-collate@7.0.0PouchDB Collate provides collation functions for PouchDB map/reduce operations to maintain consistent CouchDB collation ordering. It offers four key functions for converting objects to serialized strings that maintain proper sort order, parsing those strings back to objects, comparing objects with proper ordering semantics, and normalizing objects to match CouchDB expectations.
npm install pouchdb-collateimport { collate, normalizeKey, toIndexableString, parseIndexableString } from "pouchdb-collate";For CommonJS:
const { collate, normalizeKey, toIndexableString, parseIndexableString } = require("pouchdb-collate");import { collate, toIndexableString, parseIndexableString, normalizeKey } from "pouchdb-collate";
// Compare objects with proper CouchDB ordering
const comparison = collate("apple", "banana"); // -1 (apple < banana)
const sameComparison = collate([1, 2], [1, 2]); // 0 (equal)
// Convert objects to sortable strings
const sortableId = toIndexableString([67, true, "McDuck", "Scrooge"]);
// Result: '5323256.70000000000000017764\u000021\u00004McDuck\u00004Scrooge\u0000\u0000'
// Parse strings back to original objects
const originalData = parseIndexableString(sortableId);
// Result: [67, true, "McDuck", "Scrooge"]
// Normalize keys for CouchDB compatibility
const normalized = normalizeKey(undefined); // null
const dateNormalized = normalizeKey(new Date("2023-01-01")); // "2023-01-01T00:00:00.000Z"Compares two objects using CouchDB collation ordering rules.
/**
* Compares two objects using CouchDB collation ordering
* @param {any} a - First object to compare
* @param {any} b - Second object to compare
* @returns {number} Number indicating comparison result (-1, 0, 1)
*/
function collate(a, b);CouchDB collation order: null < boolean < number < string < array < object
Usage Examples:
// Basic comparisons
collate(null, false); // -1 (null comes before boolean)
collate(42, "hello"); // -1 (number comes before string)
collate([1, 2], {a: 1}); // -1 (array comes before object)
// String comparison
collate("apple", "banana"); // -1
collate("banana", "apple"); // 1
collate("same", "same"); // 0
// Array comparison (element by element)
collate([1, 2, 3], [1, 2, 4]); // -1
collate([1, 2], [1, 2, 3]); // -1 (shorter array comes first)
// Object comparison (by keys and values)
collate({a: 1, b: 2}, {a: 1, b: 3}); // -1
collate({a: 1}, {a: 1, b: 2}); // -1 (fewer keys comes first)Converts any object to a serialized string that maintains proper CouchDB collation ordering for lexical sorting.
/**
* Converts any object to a serialized string maintaining CouchDB collation ordering
* @param {any} key - Object to convert to indexable string
* @returns {string} String representation suitable for lexical sorting
*/
function toIndexableString(key);Usage Examples:
// Create sortable document IDs
const docId1 = toIndexableString([25, true, "Smith", "John"]);
const docId2 = toIndexableString([30, false, "Doe", "Jane"]);
// These strings will sort lexically in the same order as collate() would sort the original objects
console.log(docId1 < docId2); // true (matches collate([25, true, "Smith", "John"], [30, false, "Doe", "Jane"]) < 0)
// Handle different data types
toIndexableString(null); // "1\u0000"
toIndexableString(true); // "21\u0000"
toIndexableString(42); // "532342.00000000000000000000\u0000"
toIndexableString("hello"); // "4hello\u0000"
toIndexableString([1, 2]); // "5532141.00000000000000000000532242.00000000000000000000\u0000"
// For CouchDB compatibility, replace null bytes with another separator
const couchDbSafeId = toIndexableString([1, 2, 3]).replace(/\u0000/g, '\u0001');Reverses the toIndexableString operation, converting a serialized string back to its original structured object.
/**
* Converts an indexable string back to its original structured object
* @param {string} str - Indexable string created by toIndexableString
* @returns {any} Original JavaScript value
* @throws {Error} If the string format is invalid
*/
function parseIndexableString(str);Usage Examples:
// Round-trip conversion
const originalData = [67, true, "McDuck", "Scrooge"];
const serialized = toIndexableString(originalData);
const restored = parseIndexableString(serialized);
console.log(restored); // [67, true, "McDuck", "Scrooge"]
// Parse different data types
parseIndexableString("1\u0000"); // null
parseIndexableString("21\u0000"); // true
parseIndexableString("20\u0000"); // false
parseIndexableString("532342.00000000000000000000\u0000"); // 42
// Error handling
try {
parseIndexableString("invalid-string");
} catch (error) {
console.error("Invalid indexable string format");
}Normalizes objects to match CouchDB expectations by converting undefined to null, NaN/Infinity to null, and Date objects to JSON strings.
/**
* Normalizes objects to match CouchDB expectations
* @param {any} key - Object to normalize
* @returns {any} Normalized version compatible with CouchDB
*/
function normalizeKey(key);Usage Examples:
// Handle undefined and special values
normalizeKey(undefined); // null
normalizeKey(NaN); // null
normalizeKey(Infinity); // null
normalizeKey(-Infinity); // null
// Convert dates to JSON strings
normalizeKey(new Date("2023-01-01")); // "2023-01-01T00:00:00.000Z"
// Recursively normalize arrays and objects
normalizeKey([1, undefined, new Date("2023-01-01")]);
// [1, null, "2023-01-01T00:00:00.000Z"]
normalizeKey({
name: "John",
birthDate: new Date("1990-01-01"),
score: undefined
});
// { name: "John", birthDate: "1990-01-01T00:00:00.000Z" }
// Note: undefined properties are omitted
// Equivalent to JSON.parse(JSON.stringify(obj)) but faster
const fastNormalized = normalizeKey(complexObject);
const jsonNormalized = JSON.parse(JSON.stringify(complexObject));
// Results are equivalent// All functions accept any JavaScript value including:
// null, undefined, boolean, number, string, Date, arrays, and objects
// Comparison functions return one of: -1, 0, 1
// Indexable strings are encoded strings with specific format for sortingThe package throws errors in the following cases:
parseIndexableString() throws an Error if given a malformed indexable string// Sort by age (ascending), then by gender (descending), then by name (ascending)
function createSortKey(person) {
return toIndexableString([
person.age,
!person.male, // Invert boolean for descending order
person.lastName,
person.firstName
]);
}
const people = [
{ age: 25, male: true, lastName: "Smith", firstName: "John" },
{ age: 25, male: false, lastName: "Smith", firstName: "Jane" },
{ age: 30, male: true, lastName: "Doe", firstName: "Bob" }
];
// Create sortable IDs
people.forEach(person => {
person._id = createSortKey(person);
});
// Now people can be sorted lexically by _id and maintain proper orderingObjects are sorted according to CouchDB collation rules: