mongoose-sort-encrypted-field
Advanced tools
Comparing version 0.0.8-a to 0.0.9
@@ -5,2 +5,6 @@ declare const Base2N: any; | ||
add: Function; | ||
options: { | ||
sortFields: {}; | ||
decrypters: {}; | ||
}; | ||
paths: { | ||
@@ -14,3 +18,6 @@ [fieldName: string]: { | ||
}; | ||
}, options: any): void; | ||
declare function evaluateMissedSortFields(model: any): Promise<void>; | ||
}, options?: { | ||
noOfDividePartsForSearching?: number; | ||
noOfCharsToIncreaseOnSaturation?: number; | ||
}): void; | ||
declare function evaluateMissedSortFields(model: any): void; |
417
lib/index.js
@@ -10,43 +10,29 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { | ||
}; | ||
var __generator = (this && this.__generator) || function (thisArg, body) { | ||
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; | ||
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; | ||
function verb(n) { return function (v) { return step([n, v]); }; } | ||
function step(op) { | ||
if (f) throw new TypeError("Generator is already executing."); | ||
while (g && (g = 0, op[0] && (_ = 0)), _) try { | ||
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; | ||
if (y = 0, t) op = [op[0] & 2, t.value]; | ||
switch (op[0]) { | ||
case 0: case 1: t = op; break; | ||
case 4: _.label++; return { value: op[1], done: false }; | ||
case 5: _.label++; y = op[1]; op = [0]; continue; | ||
case 7: op = _.ops.pop(); _.trys.pop(); continue; | ||
default: | ||
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } | ||
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } | ||
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } | ||
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } | ||
if (t[2]) _.ops.pop(); | ||
_.trys.pop(); continue; | ||
} | ||
op = body.call(thisArg, _); | ||
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } | ||
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; | ||
const Base2N = require('@navpreetdevpuri/base-2-n'); | ||
function sortEncryptedFields(schema, options = { noOfDividePartsForSearching: 100, noOfCharsToIncreaseOnSaturation: 2 }) { | ||
const { noOfDividePartsForSearching = 100, noOfCharsToIncreaseOnSaturation = 2, } = options; | ||
const sortFields = {}; | ||
const decrypters = {}; | ||
for (const [fieldName, field] of Object.entries(schema.paths)) { | ||
if (!field.options.sortFieldName) | ||
continue; | ||
if (!sortFields[fieldName]) | ||
sortFields[fieldName] = field.options.sortFieldName; | ||
if (!decrypters[fieldName]) | ||
decrypters[fieldName] = field.options.get; | ||
schema.add({ | ||
[field.options.sortFieldName]: { | ||
type: String, | ||
default: null, | ||
}, | ||
}); | ||
} | ||
}; | ||
var Base2N = require('@navpreetdevpuri/base-2-n'); | ||
function sortEncryptedFields(schema, options) { | ||
var _a; | ||
var _b = options.noOfDividePartsForSearching, noOfDividePartsForSearching = _b === void 0 ? 100 : _b, _c = options.noOfCharsToIncreaseOnSaturation, noOfCharsToIncreaseOnSaturation = _c === void 0 ? 2 : _c; | ||
options.noOfDividePartsForSearching = noOfDividePartsForSearching; | ||
options.noOfCharsToIncreaseOnSaturation = noOfCharsToIncreaseOnSaturation; | ||
var sortFields = {}; | ||
var decrypters = {}; | ||
schema.options.sortFields = sortFields; | ||
schema.options.decrypters = decrypters; | ||
function documentsBinarySearch(documents, fieldName, value) { | ||
var start = 0; | ||
var end = documents.length - 1; | ||
let start = 0; | ||
let end = documents.length - 1; | ||
while (start <= end) { | ||
var mid = Math.floor((start + end) / 2); | ||
var decryptedMidValue = decrypters[fieldName](documents[mid][fieldName]).toLowerCase(); | ||
const mid = Math.floor((start + end) / 2); | ||
const decryptedMidValue = decrypters[fieldName](documents[mid][fieldName]).toLowerCase(); | ||
if (value < decryptedMidValue) { | ||
@@ -68,8 +54,8 @@ end = mid - 1; | ||
} | ||
var predecessorNumber; | ||
var successorNumber; | ||
let predecessorNumber; | ||
let successorNumber; | ||
if (predecessorSortId.length == predecessorSortId.length) { | ||
predecessorNumber = new Base2N(predecessorSortId); | ||
successorNumber = new Base2N(successorSortId); | ||
var averageNumber = predecessorNumber.average(successorNumber); | ||
const averageNumber = predecessorNumber.average(successorNumber); | ||
if (averageNumber.toString() != predecessorNumber.toString()) { | ||
@@ -82,202 +68,127 @@ return averageNumber.toString(); | ||
} | ||
var bigger = predecessorSortId.length > successorSortId.length | ||
const bigger = predecessorSortId.length > successorSortId.length | ||
? predecessorSortId | ||
: successorSortId; | ||
var smaller = successorSortId.length > predecessorSortId.length | ||
const smaller = successorSortId.length > predecessorSortId.length | ||
? predecessorSortId | ||
: successorSortId; | ||
var biggerNumber = new Base2N(bigger); | ||
var smallerNumber = new Base2N(smaller.padEnd(bigger.length, '\0')); | ||
const biggerNumber = new Base2N(bigger); | ||
const smallerNumber = new Base2N(smaller.padEnd(bigger.length, '\0')); | ||
return biggerNumber.average(smallerNumber).toString(); | ||
} | ||
function getMatchForAggregate(documents, fieldName, fieldValue, sortFieldName) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var index, gteIndex, lteIndex, match; | ||
var _a, _b; | ||
return __generator(this, function (_c) { | ||
index = documentsBinarySearch(documents, fieldName, fieldValue); | ||
gteIndex = index - 1; | ||
lteIndex = index; | ||
match = { | ||
$and: [ | ||
(_a = {}, _a[sortFieldName] = { $ne: null }, _a), | ||
(_b = {}, | ||
_b[sortFieldName] = { | ||
$gte: gteIndex == -1 ? undefined : documents[gteIndex][sortFieldName], | ||
$lte: lteIndex == documents.length | ||
? undefined | ||
: documents[lteIndex][sortFieldName], | ||
}, | ||
_b), | ||
], | ||
}; | ||
return [2 /*return*/, match]; | ||
}); | ||
function getMatchForAggregate(documents, fieldName, fieldValue, sortFieldName, ignoreSortFieldValue) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const index = documentsBinarySearch(documents, fieldName, fieldValue); | ||
const gteIndex = index - 1; | ||
const lteIndex = index; | ||
const match = { | ||
$and: [{ [sortFieldName]: { $ne: ignoreSortFieldValue } }], | ||
}; | ||
if (gteIndex === -1 && lteIndex === documents.length) { | ||
return match; | ||
} | ||
match.$and.push({ [sortFieldName]: {} }); | ||
if (gteIndex !== -1) { | ||
match.$and[1][sortFieldName].$gte = documents[gteIndex][sortFieldName]; | ||
} | ||
if (lteIndex !== documents.length) { | ||
match.$and[1][sortFieldName].$lte = documents[lteIndex][sortFieldName]; | ||
} | ||
return match; | ||
}); | ||
} | ||
function updateSortFieldsForDocument(objectId, model, fieldName, fieldValue, sortFieldName) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var pipeline, currN, _a, _b, match, documents, index, gteIndex, lteIndex, predecessorSortId, successorSortId, newSortId, documentsCountWithSameSortId, documentsWithSameSortId, _i, documentsWithSameSortId_1, document_1; | ||
var _c, _d, _e, _f, _g, _h, _j, _k; | ||
return __generator(this, function (_l) { | ||
switch (_l.label) { | ||
case 0: | ||
console.time("Total time took to update orderId: ".concat(objectId)); | ||
pipeline = []; | ||
_b = (_a = Math).round; | ||
return [4 /*yield*/, model.find({}).count().exec()]; | ||
case 1: | ||
currN = _b.apply(_a, [(_l.sent()) / noOfDividePartsForSearching]); | ||
match = { $and: (_c = {}, _c[sortFieldName] = { $ne: null }, _c) }; | ||
_l.label = 2; | ||
case 2: | ||
if (!true) return [3 /*break*/, 5]; | ||
return [4 /*yield*/, model.aggregate([ | ||
{ $match: match }, | ||
{ | ||
$setWindowFields: { | ||
sortBy: (_d = {}, _d[sortFieldName] = 1, _d), | ||
output: { | ||
index: { $rank: {} }, | ||
}, | ||
}, | ||
}, | ||
{ | ||
$match: { | ||
$expr: { | ||
$eq: [{ $mod: ['$index', currN] }, 0], | ||
}, | ||
}, | ||
}, | ||
{ $project: (_e = {}, _e[fieldName] = 1, _e[sortFieldName] = 1, _e) }, | ||
])]; | ||
case 3: | ||
documents = _l.sent(); | ||
if (currN < noOfDividePartsForSearching) { | ||
return [3 /*break*/, 5]; | ||
} | ||
return [4 /*yield*/, getMatchForAggregate(documents, fieldName, fieldValue, sortFieldName)]; | ||
case 4: | ||
match = _l.sent(); | ||
currN = Math.round(currN / noOfDividePartsForSearching); | ||
return [3 /*break*/, 2]; | ||
case 5: | ||
index = documentsBinarySearch(documents, fieldName, fieldValue); | ||
gteIndex = index - 1; | ||
lteIndex = index; | ||
predecessorSortId = documents[gteIndex][sortFieldName]; | ||
successorSortId = documents[lteIndex][sortFieldName]; | ||
newSortId = getAverageSortId(predecessorSortId, successorSortId); | ||
return [4 /*yield*/, model.updateOne({ _id: objectId }, { $set: (_f = {}, _f[sortFieldName] = newSortId.toString(), _f) })]; | ||
case 6: | ||
_l.sent(); | ||
return [4 /*yield*/, model | ||
.find((_g = {}, _g[sortFieldName] = newSortId.toString(), _g)) | ||
.count() | ||
.exec()]; | ||
case 7: | ||
documentsCountWithSameSortId = _l.sent(); | ||
if (!(documentsCountWithSameSortId > 1)) return [3 /*break*/, 12]; | ||
return [4 /*yield*/, model | ||
.find((_h = {}, _h[sortFieldName] = newSortId.toString(), _h), (_j = { _id: 1 }, _j[fieldName] = 1, _j)) | ||
.exec()]; | ||
case 8: | ||
documentsWithSameSortId = _l.sent(); | ||
console.log("mongoose-sort-encrypted-field -> Got collions, retrying... ".concat(documentsWithSameSortId)); | ||
_i = 0, documentsWithSameSortId_1 = documentsWithSameSortId; | ||
_l.label = 9; | ||
case 9: | ||
if (!(_i < documentsWithSameSortId_1.length)) return [3 /*break*/, 12]; | ||
document_1 = documentsWithSameSortId_1[_i]; | ||
// Retrigering sortId generation due to collion | ||
return [4 /*yield*/, model.updateOne({ _id: document_1._id }, { $set: (_k = {}, _k[fieldName] = document_1[fieldName], _k) })]; | ||
case 10: | ||
// Retrigering sortId generation due to collion | ||
_l.sent(); | ||
_l.label = 11; | ||
case 11: | ||
_i++; | ||
return [3 /*break*/, 9]; | ||
case 12: | ||
console.timeEnd("Total time took to update orderId: ".concat(objectId)); | ||
return [2 /*return*/]; | ||
} | ||
}); | ||
function updateSortFieldsForDocument(objectId, model, fieldName, fieldValue, sortFieldName, ignoreSortFieldValue = null) { | ||
return __awaiter(this, void 0, void 0, function* () { | ||
console.time(`Total time took to update orderId: ${objectId}`); | ||
const pipeline = []; | ||
let currN = Math.round((yield model.find({}).count().exec()) / noOfDividePartsForSearching); | ||
let match = { | ||
$and: [{ [sortFieldName]: { $ne: ignoreSortFieldValue } }], | ||
}; | ||
let documents = []; | ||
while (currN > 0) { | ||
documents = yield model.aggregate([ | ||
{ $match: match }, | ||
{ | ||
$setWindowFields: { | ||
sortBy: { [sortFieldName]: 1 }, | ||
output: { | ||
index: { $rank: {} }, | ||
}, | ||
}, | ||
}, | ||
{ | ||
$match: { | ||
$expr: { | ||
$eq: [{ $mod: ['$index', currN] }, 0], | ||
}, | ||
}, | ||
}, | ||
{ $project: { [fieldName]: 1, [sortFieldName]: 1 } }, | ||
]); | ||
match = yield getMatchForAggregate(documents, fieldName, fieldValue, sortFieldName, ignoreSortFieldValue); | ||
currN = Math.round(currN / noOfDividePartsForSearching); | ||
} | ||
match = yield getMatchForAggregate(documents, fieldName, fieldValue, sortFieldName, ignoreSortFieldValue); | ||
documents = yield model | ||
.aggregate([ | ||
{ $match: match }, | ||
{ $project: { [fieldName]: 1, [sortFieldName]: 1 } }, | ||
{ $sort: { [sortFieldName]: 1 } }, | ||
]) | ||
.exec(); | ||
const index = documentsBinarySearch(documents, fieldName, fieldValue); | ||
let gteIndex = index - 1; | ||
let lteIndex = index; | ||
const predecessorSortId = documents[gteIndex] && documents[gteIndex][sortFieldName]; | ||
const successorSortId = documents[lteIndex] && documents[lteIndex][sortFieldName]; | ||
const newSortId = getAverageSortId(predecessorSortId, successorSortId); | ||
yield model.updateOne({ _id: objectId }, { $set: { [sortFieldName]: newSortId.toString() } }); | ||
const documentsCountWithSameSortId = yield model | ||
.find({ [sortFieldName]: newSortId.toString() }) | ||
.count() | ||
.exec(); | ||
if (documentsCountWithSameSortId > 1) { | ||
console.log(`mongoose-sort-encrypted-field -> Got collions, retrying... ${objectId}`); | ||
// Retrigering sortId generation due to collion | ||
console.timeEnd(`Total time took to update orderId: ${objectId}`); | ||
yield model.updateOne({ _id: objectId }, { $set: { [fieldName]: decrypters[fieldName][document[fieldName]] } }); | ||
} | ||
console.timeEnd(`Total time took to update orderId: ${objectId}`); | ||
}); | ||
} | ||
function updateSortFieldsForUpdateOne(filter, model, fieldName, fieldValue, sortFieldName) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var document; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, model.findOne(filter, { _id: 1 }).exec()]; | ||
case 1: | ||
document = _a.sent(); | ||
if (!document) return [3 /*break*/, 3]; | ||
return [4 /*yield*/, updateSortFieldsForDocument(document._id, model, fieldName, fieldValue, sortFieldName)]; | ||
case 2: | ||
_a.sent(); | ||
_a.label = 3; | ||
case 3: return [2 /*return*/]; | ||
} | ||
}); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const document = yield model | ||
.findOne(filter, { _id: 1, [sortFieldName]: 1 }) | ||
.exec(); | ||
if (document) { | ||
yield updateSortFieldsForDocument(document._id, model, fieldName, fieldValue, sortFieldName, document[sortFieldName]); | ||
} | ||
}); | ||
} | ||
function updateSortFieldsForUpdateMany(filter, model, fieldName, fieldValue, sortFieldName) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var documents, i; | ||
return __generator(this, function (_a) { | ||
switch (_a.label) { | ||
case 0: return [4 /*yield*/, model.find(filter, { _id: 1 }).exec()]; | ||
case 1: | ||
documents = _a.sent(); | ||
if (!(documents && documents.length > 0)) return [3 /*break*/, 5]; | ||
i = 0; | ||
_a.label = 2; | ||
case 2: | ||
if (!(i < documents.length)) return [3 /*break*/, 5]; | ||
return [4 /*yield*/, updateSortFieldsForDocument(documents[i]._id, model, fieldName, fieldValue, sortFieldName)]; | ||
case 3: | ||
_a.sent(); | ||
_a.label = 4; | ||
case 4: | ||
i += 1; | ||
return [3 /*break*/, 2]; | ||
case 5: return [2 /*return*/]; | ||
return __awaiter(this, void 0, void 0, function* () { | ||
const documents = yield model | ||
.find(filter, { _id: 1, [sortFieldName]: 1 }) | ||
.exec(); | ||
if (documents && documents.length > 0) { | ||
for (let i = 0; i < documents.length; i += 1) { | ||
yield updateSortFieldsForDocument(documents[i]._id, model, fieldName, decrypters[fieldName](fieldValue), sortFieldName, documents[i][sortFieldName]); | ||
} | ||
}); | ||
} | ||
}); | ||
} | ||
for (var _i = 0, _d = Object.entries(schema.paths); _i < _d.length; _i++) { | ||
var _e = _d[_i], fieldName = _e[0], field = _e[1]; | ||
if (field.options.sortFieldName) { | ||
sortFields[fieldName] = field.options.sortFieldName; | ||
decrypters[fieldName] = field.options.get; | ||
schema.add((_a = {}, | ||
_a[field.options.sortFieldName] = { | ||
type: String, | ||
default: null, | ||
}, | ||
_a)); | ||
} | ||
} | ||
options.sortFields = sortFields; | ||
options.decrypters = decrypters; | ||
schema.post('save', function (doc, next) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var _i, _a, _b, fieldName, sortFieldName; | ||
return __generator(this, function (_c) { | ||
for (_i = 0, _a = Object.entries(sortFields); _i < _a.length; _i++) { | ||
_b = _a[_i], fieldName = _b[0], sortFieldName = _b[1]; | ||
updateSortFieldsForDocument(doc._id, this.constructor, fieldName, doc[fieldName], sortFieldName); | ||
} | ||
next(); | ||
return [2 /*return*/]; | ||
}); | ||
return __awaiter(this, void 0, void 0, function* () { | ||
for (const [fieldName, sortFieldName] of Object.entries(sortFields)) { | ||
updateSortFieldsForDocument(doc._id, this.constructor, fieldName, doc[fieldName], sortFieldName); | ||
} | ||
next(); | ||
}); | ||
}); | ||
schema.post('updateOne', function (res, next) { | ||
var update = this.getUpdate(); | ||
for (var _i = 0, _a = Object.entries(sortFields); _i < _a.length; _i++) { | ||
var _b = _a[_i], fieldName = _b[0], sortFieldName = _b[1]; | ||
const update = this.getUpdate(); | ||
for (const fieldName of Object.keys(sortFields)) { | ||
const sortFieldName = sortFields[fieldName]; | ||
if (update.$set && update.$set[sortFieldName]) { | ||
@@ -288,3 +199,3 @@ // Bypass middleware internal call for updating any sortFieldName field | ||
if (update.$set && update.$set[fieldName]) { | ||
updateSortFieldsForUpdateOne(this.getFilter(), this.constructor, fieldName, update.$set[fieldName], sortFieldName); | ||
updateSortFieldsForUpdateOne(this.getFilter(), this.model, fieldName, decrypters[fieldName](update.$set[fieldName]), sortFieldName); | ||
} | ||
@@ -295,6 +206,5 @@ } | ||
schema.post('updateMany', function (res, next) { | ||
var update = this.getUpdate(); | ||
for (var _i = 0, _a = Object.keys(sortFields); _i < _a.length; _i++) { | ||
var fieldName = _a[_i]; | ||
var sortFieldName = sortFields[fieldName]; | ||
const update = this.getUpdate(); | ||
for (const fieldName of Object.keys(sortFields)) { | ||
const sortFieldName = sortFields[fieldName]; | ||
if (update.$set && update.$set[sortFieldName]) { | ||
@@ -305,3 +215,3 @@ // Bypass middleware internal call for updating any sortFieldName field | ||
if (update.$set && update.$set[fieldName]) { | ||
updateSortFieldsForUpdateMany(this.getFilter(), this.constructor, fieldName, update.$set[fieldName], sortFieldName); | ||
updateSortFieldsForUpdateMany(this.getFilter(), this.model, fieldName, decrypters[fieldName](update.$set[fieldName]), sortFieldName); | ||
} | ||
@@ -313,43 +223,18 @@ } | ||
function evaluateMissedSortFields(model) { | ||
return __awaiter(this, void 0, void 0, function () { | ||
var pluginOptions, _i, _a, fieldName, sortFieldName, documents, _b, documents_1, document_2; | ||
var _c, _d; | ||
return __generator(this, function (_e) { | ||
switch (_e.label) { | ||
case 0: | ||
pluginOptions = model.schema.plugins.find(function (plugin) { return plugin.fn.name == 'sortEncryptedFields'; }); | ||
if (!pluginOptions) { | ||
throw 'Plugin is not enabled on this model, Try ModelSchema.plugin(sortEncryptedFields), brefore creating Model.'; | ||
} | ||
_i = 0, _a = Object.keys(pluginOptions.sortFields); | ||
_e.label = 1; | ||
case 1: | ||
if (!(_i < _a.length)) return [3 /*break*/, 7]; | ||
fieldName = _a[_i]; | ||
sortFieldName = pluginOptions.sortFields[fieldName]; | ||
return [4 /*yield*/, model.find((_c = {}, _c[sortFieldName] = { $eq: null }, _c))]; | ||
case 2: | ||
documents = _e.sent(); | ||
_b = 0, documents_1 = documents; | ||
_e.label = 3; | ||
case 3: | ||
if (!(_b < documents_1.length)) return [3 /*break*/, 6]; | ||
document_2 = documents_1[_b]; | ||
// Retrigering sortId generation | ||
return [4 /*yield*/, model.updateOne({ _id: document_2._id }, { $set: (_d = {}, _d[fieldName] = document_2[fieldName], _d) })]; | ||
case 4: | ||
// Retrigering sortId generation | ||
_e.sent(); | ||
_e.label = 5; | ||
case 5: | ||
_b++; | ||
return [3 /*break*/, 3]; | ||
case 6: | ||
_i++; | ||
return [3 /*break*/, 1]; | ||
case 7: return [2 /*return*/]; | ||
const plugin = model.schema.plugins.find((plugin) => plugin.fn.name == 'sortEncryptedFields'); | ||
if (!plugin) { | ||
throw 'Plugin is not enabled on this model, Try ModelSchema.plugin(sortEncryptedFields), brefore creating Model.'; | ||
} | ||
model.schema.options.model = model; | ||
const { sortFields, decrypters } = model.schema.options; | ||
for (const fieldName of Object.keys(sortFields)) { | ||
const sortFieldName = sortFields[fieldName]; | ||
model.find({ [sortFieldName]: { $eq: null } }).then((documents) => __awaiter(this, void 0, void 0, function* () { | ||
for (const document of documents) { | ||
// Retrigering sortId generation | ||
yield model.updateOne({ _id: document._id }, { $set: { [fieldName]: decrypters[fieldName](document[fieldName]) } }); | ||
} | ||
}); | ||
}); | ||
})); | ||
} | ||
} | ||
module.exports = { sortEncryptedFields: sortEncryptedFields, evaluateMissedSortFields: evaluateMissedSortFields }; | ||
module.exports = { sortEncryptedFields, evaluateMissedSortFields }; |
{ | ||
"name": "mongoose-sort-encrypted-field", | ||
"version": "0.0.8a", | ||
"version": "0.0.9", | ||
"description": "Mongoose plugin to enable sorting on encrypted fields", | ||
@@ -5,0 +5,0 @@ "main": "lib/index.js", |
@@ -47,3 +47,3 @@ # mongoose-sort-encrypted-field | ||
```javascript | ||
const await sortedUsers = await User.find({}).sort({ emailSort: 1 }).exec(); | ||
const sortedUsers = await User.find({}).sort({ emailSort: 1 }).exec(); | ||
``` | ||
@@ -50,0 +50,0 @@ |
190
src/index.ts
@@ -7,2 +7,3 @@ const Base2N = require('@navpreetdevpuri/base-2-n'); | ||
add: Function; | ||
options: { sortFields: {}; decrypters: {} }; | ||
paths: { | ||
@@ -14,3 +15,6 @@ [fieldName: string]: { | ||
}, | ||
options | ||
options: { | ||
noOfDividePartsForSearching?: number; | ||
noOfCharsToIncreaseOnSaturation?: number; | ||
} = { noOfDividePartsForSearching: 100, noOfCharsToIncreaseOnSaturation: 2 } | ||
) { | ||
@@ -21,8 +25,22 @@ const { | ||
} = options; | ||
options.noOfDividePartsForSearching = noOfDividePartsForSearching; | ||
options.noOfCharsToIncreaseOnSaturation = noOfCharsToIncreaseOnSaturation; | ||
const sortFields: { [key: string]: string } = {}; | ||
const sortFields = {}; | ||
const decrypters = {}; | ||
for (const [fieldName, field] of Object.entries(schema.paths)) { | ||
if (!field.options.sortFieldName) continue; | ||
if (!sortFields[fieldName]) | ||
sortFields[fieldName] = field.options.sortFieldName; | ||
if (!decrypters[fieldName]) decrypters[fieldName] = field.options.get; | ||
schema.add({ | ||
[field.options.sortFieldName]: { | ||
type: String, | ||
default: null, | ||
}, | ||
}); | ||
} | ||
schema.options.sortFields = sortFields; | ||
schema.options.decrypters = decrypters; | ||
function documentsBinarySearch(documents, fieldName, value) { | ||
@@ -100,3 +118,4 @@ let start = 0; | ||
fieldValue, | ||
sortFieldName | ||
sortFieldName, | ||
ignoreSortFieldValue | ||
) { | ||
@@ -106,18 +125,19 @@ const index = documentsBinarySearch(documents, fieldName, fieldValue); | ||
const lteIndex = index; | ||
const match = { | ||
$and: [ | ||
{ [sortFieldName]: { $ne: null } }, | ||
{ | ||
[sortFieldName]: { | ||
$gte: | ||
gteIndex == -1 ? undefined : documents[gteIndex][sortFieldName], | ||
$lte: | ||
lteIndex == documents.length | ||
? undefined | ||
: documents[lteIndex][sortFieldName], | ||
}, | ||
}, | ||
], | ||
const match: any = { | ||
$and: [{ [sortFieldName]: { $ne: ignoreSortFieldValue } }], | ||
}; | ||
if (gteIndex === -1 && lteIndex === documents.length) { | ||
return match; | ||
} | ||
match.$and.push({ [sortFieldName]: {} }); | ||
if (gteIndex !== -1) { | ||
match.$and[1][sortFieldName].$gte = documents[gteIndex][sortFieldName]; | ||
} | ||
if (lteIndex !== documents.length) { | ||
match.$and[1][sortFieldName].$lte = documents[lteIndex][sortFieldName]; | ||
} | ||
return match; | ||
@@ -131,3 +151,4 @@ } | ||
fieldValue, | ||
sortFieldName | ||
sortFieldName, | ||
ignoreSortFieldValue = null | ||
) { | ||
@@ -139,5 +160,8 @@ console.time(`Total time took to update orderId: ${objectId}`); | ||
); | ||
let match: {} = { $and: { [sortFieldName]: { $ne: null } } }; | ||
let documents; | ||
while (true) { | ||
let match: {} = { | ||
$and: [{ [sortFieldName]: { $ne: ignoreSortFieldValue } }], | ||
}; | ||
let documents = []; | ||
while (currN > 0) { | ||
documents = await model.aggregate([ | ||
@@ -163,5 +187,2 @@ { $match: match }, | ||
if (currN < noOfDividePartsForSearching) { | ||
break; | ||
} | ||
match = await getMatchForAggregate( | ||
@@ -171,6 +192,21 @@ documents, | ||
fieldValue, | ||
sortFieldName | ||
sortFieldName, | ||
ignoreSortFieldValue | ||
); | ||
currN = Math.round(currN / noOfDividePartsForSearching); | ||
} | ||
match = await getMatchForAggregate( | ||
documents, | ||
fieldName, | ||
fieldValue, | ||
sortFieldName, | ||
ignoreSortFieldValue | ||
); | ||
documents = await model | ||
.aggregate([ | ||
{ $match: match }, | ||
{ $project: { [fieldName]: 1, [sortFieldName]: 1 } }, | ||
{ $sort: { [sortFieldName]: 1 } }, | ||
]) | ||
.exec(); | ||
@@ -180,4 +216,6 @@ const index = documentsBinarySearch(documents, fieldName, fieldValue); | ||
let lteIndex = index; | ||
const predecessorSortId = documents[gteIndex][sortFieldName]; | ||
const successorSortId = documents[lteIndex][sortFieldName]; | ||
const predecessorSortId = | ||
documents[gteIndex] && documents[gteIndex][sortFieldName]; | ||
const successorSortId = | ||
documents[lteIndex] && documents[lteIndex][sortFieldName]; | ||
const newSortId = getAverageSortId(predecessorSortId, successorSortId); | ||
@@ -193,18 +231,12 @@ await model.updateOne( | ||
if (documentsCountWithSameSortId > 1) { | ||
const documentsWithSameSortId = await model | ||
.find( | ||
{ [sortFieldName]: newSortId.toString() }, | ||
{ _id: 1, [fieldName]: 1 } | ||
) | ||
.exec(); | ||
console.log( | ||
`mongoose-sort-encrypted-field -> Got collions, retrying... ${documentsWithSameSortId}` | ||
`mongoose-sort-encrypted-field -> Got collions, retrying... ${objectId}` | ||
); | ||
for (const document of documentsWithSameSortId) { | ||
// Retrigering sortId generation due to collion | ||
await model.updateOne( | ||
{ _id: document._id }, | ||
{ $set: { [fieldName]: document[fieldName] } } | ||
); | ||
} | ||
// Retrigering sortId generation due to collion | ||
console.timeEnd(`Total time took to update orderId: ${objectId}`); | ||
await model.updateOne( | ||
{ _id: objectId }, | ||
{ $set: { [fieldName]: decrypters[fieldName][document[fieldName]] } } | ||
); | ||
} | ||
@@ -221,3 +253,5 @@ console.timeEnd(`Total time took to update orderId: ${objectId}`); | ||
) { | ||
const document = await model.findOne(filter, { _id: 1 }).exec(); | ||
const document = await model | ||
.findOne(filter, { _id: 1, [sortFieldName]: 1 }) | ||
.exec(); | ||
if (document) { | ||
@@ -229,3 +263,4 @@ await updateSortFieldsForDocument( | ||
fieldValue, | ||
sortFieldName | ||
sortFieldName, | ||
document[sortFieldName] | ||
); | ||
@@ -242,3 +277,5 @@ } | ||
) { | ||
const documents = await model.find(filter, { _id: 1 }).exec(); | ||
const documents = await model | ||
.find(filter, { _id: 1, [sortFieldName]: 1 }) | ||
.exec(); | ||
if (documents && documents.length > 0) { | ||
@@ -250,4 +287,5 @@ for (let i = 0; i < documents.length; i += 1) { | ||
fieldName, | ||
fieldValue, | ||
sortFieldName | ||
decrypters[fieldName](fieldValue), | ||
sortFieldName, | ||
documents[i][sortFieldName] | ||
); | ||
@@ -258,18 +296,2 @@ } | ||
for (const [fieldName, field] of Object.entries(schema.paths)) { | ||
if (field.options.sortFieldName) { | ||
sortFields[fieldName] = field.options.sortFieldName; | ||
decrypters[fieldName] = field.options.get; | ||
schema.add({ | ||
[field.options.sortFieldName]: { | ||
type: String, | ||
default: null, | ||
}, | ||
}); | ||
} | ||
} | ||
options.sortFields = sortFields; | ||
options.decrypters = decrypters; | ||
schema.post('save', async function (doc, next) { | ||
@@ -290,3 +312,4 @@ for (const [fieldName, sortFieldName] of Object.entries(sortFields)) { | ||
const update: { $set: { [key: string]: string } } = this.getUpdate(); | ||
for (const [fieldName, sortFieldName] of Object.entries(sortFields)) { | ||
for (const fieldName of Object.keys(sortFields)) { | ||
const sortFieldName = sortFields[fieldName]; | ||
if (update.$set && update.$set[sortFieldName]) { | ||
@@ -299,5 +322,5 @@ // Bypass middleware internal call for updating any sortFieldName field | ||
this.getFilter(), | ||
this.constructor, | ||
this.model, | ||
fieldName, | ||
update.$set[fieldName], | ||
decrypters[fieldName](update.$set[fieldName]), | ||
sortFieldName | ||
@@ -321,5 +344,5 @@ ); | ||
this.getFilter(), | ||
this.constructor, | ||
this.model, | ||
fieldName, | ||
update.$set[fieldName], | ||
decrypters[fieldName](update.$set[fieldName]), | ||
sortFieldName | ||
@@ -333,21 +356,24 @@ ); | ||
async function evaluateMissedSortFields(model) { | ||
const pluginOptions = model.schema.plugins.find( | ||
function evaluateMissedSortFields(model) { | ||
const plugin = model.schema.plugins.find( | ||
(plugin) => plugin.fn.name == 'sortEncryptedFields' | ||
); | ||
if (!pluginOptions) { | ||
if (!plugin) { | ||
throw 'Plugin is not enabled on this model, Try ModelSchema.plugin(sortEncryptedFields), brefore creating Model.'; | ||
} | ||
for (const fieldName of Object.keys(pluginOptions.sortFields)) { | ||
const sortFieldName = pluginOptions.sortFields[fieldName]; | ||
const documents = await model.find({ [sortFieldName]: { $eq: null } }); | ||
for (const document of documents) { | ||
// Retrigering sortId generation | ||
await model.updateOne( | ||
{ _id: document._id }, | ||
{ $set: { [fieldName]: document[fieldName] } } | ||
); | ||
} | ||
model.schema.options.model = model; | ||
const { sortFields, decrypters } = model.schema.options; | ||
for (const fieldName of Object.keys(sortFields)) { | ||
const sortFieldName = sortFields[fieldName]; | ||
model.find({ [sortFieldName]: { $eq: null } }).then(async (documents) => { | ||
for (const document of documents) { | ||
// Retrigering sortId generation | ||
await model.updateOne( | ||
{ _id: document._id }, | ||
{ $set: { [fieldName]: decrypters[fieldName](document[fieldName]) } } | ||
); | ||
} | ||
}); | ||
} | ||
} | ||
module.exports = { sortEncryptedFields, evaluateMissedSortFields }; |
{ | ||
"compilerOptions": { | ||
"incremental": true, /* Enable incremental compilation */ | ||
"target": "es5", /* Specify ECMAScript target version: */ | ||
"target": "es6", /* Specify ECMAScript target version: */ | ||
"module": "commonjs", /* 'none', 'commonjs', 'amd', 'system', etc */ | ||
@@ -6,0 +6,0 @@ "declaration": true, /* Concatenate & emit output to single file.*/ |
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
Manifest confusion
Supply chain riskThis package has inconsistent metadata. This could be malicious or caused by an error when publishing the package.
Found 1 instance in 1 package
0
26751
597