@electerm/nedb 1.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +22 -0
- package/README.md +22 -0
- package/index.js +3 -0
- package/lib/async.js +237 -0
- package/lib/cursor.js +204 -0
- package/lib/customUtils.js +22 -0
- package/lib/datastore.js +705 -0
- package/lib/executor.js +78 -0
- package/lib/indexes.js +294 -0
- package/lib/model.js +835 -0
- package/lib/persistence.js +314 -0
- package/lib/storage.js +136 -0
- package/lib/underscore.js +177 -0
- package/package.json +52 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
(The MIT License)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2013 Louis Chatriot <louis.chatriot@gmail.com>
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
6
|
+
a copy of this software and associated documentation files (the
|
|
7
|
+
'Software'), to deal in the Software without restriction, including
|
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
11
|
+
the following conditions:
|
|
12
|
+
|
|
13
|
+
The above copyright notice and this permission notice shall be
|
|
14
|
+
included in all copies or substantial portions of the Software.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
|
|
20
|
+
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
|
|
21
|
+
TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
|
|
22
|
+
SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
|
|
2
|
+
# @electerm/nedb
|
|
3
|
+
|
|
4
|
+
**Note: This is an fork of [yetzt/nedb](https://github.com/yetzt/nedb), which itself is a fork of the original [nedb](https://github.com/louischatriot/nedb) by [Louis Chatriot](https://github.com/louischatriot). This version includes additional improvements and maintenance updates.**
|
|
5
|
+
|
|
6
|
+
**Embedded persistent or in memory database for Node.js, nw.js, Electron, 100% JavaScript, no binary dependency**. API is a subset of MongoDB's and it's <a href="#speed">plenty fast</a>.
|
|
7
|
+
|
|
8
|
+
## About this Fork
|
|
9
|
+
|
|
10
|
+
This fork specially for electerm use, removed async/underscore for smaller size
|
|
11
|
+
|
|
12
|
+
## Installation, tests
|
|
13
|
+
Module name on npm is `@electerm/nedb`.
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
npm install @electerm/nedb --save # Put latest version in your package.json
|
|
17
|
+
npm test # You'll need the dev dependencies to launch tests
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## License
|
|
21
|
+
|
|
22
|
+
See [License](LICENSE)
|
package/index.js
ADDED
package/lib/async.js
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Custom async utilities to replace the async library
|
|
3
|
+
* Uses modern async/await internally while maintaining callback compatibility
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Promisify a callback-based function
|
|
8
|
+
*/
|
|
9
|
+
function promisify(fn) {
|
|
10
|
+
return function(...args) {
|
|
11
|
+
return new Promise((resolve, reject) => {
|
|
12
|
+
fn(...args, (err, ...results) => {
|
|
13
|
+
if (err) reject(err);
|
|
14
|
+
else resolve(results.length <= 1 ? results[0] : results);
|
|
15
|
+
});
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Execute functions in series (waterfall pattern)
|
|
22
|
+
* Each function receives the result of the previous function as its parameters (except the last which is always the callback)
|
|
23
|
+
*/
|
|
24
|
+
function waterfall(tasks, finalCallback) {
|
|
25
|
+
finalCallback = finalCallback || function() {};
|
|
26
|
+
|
|
27
|
+
if (!Array.isArray(tasks) || tasks.length === 0) {
|
|
28
|
+
return finalCallback(null);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
let taskIndex = 0;
|
|
32
|
+
|
|
33
|
+
function runNextTask(err, ...results) {
|
|
34
|
+
if (err) {
|
|
35
|
+
return finalCallback(err);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (taskIndex >= tasks.length) {
|
|
39
|
+
return finalCallback(null, ...results);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const task = tasks[taskIndex++];
|
|
43
|
+
|
|
44
|
+
try {
|
|
45
|
+
if (typeof task === 'function') {
|
|
46
|
+
// Regular function
|
|
47
|
+
if (taskIndex === 1) {
|
|
48
|
+
// First task gets only the callback
|
|
49
|
+
task(runNextTask);
|
|
50
|
+
} else {
|
|
51
|
+
// Subsequent tasks get results from previous task, then callback
|
|
52
|
+
task(...results, runNextTask);
|
|
53
|
+
}
|
|
54
|
+
} else if (task && task.__async_apply) {
|
|
55
|
+
// Handle async.apply case
|
|
56
|
+
const args = [...task.args];
|
|
57
|
+
if (taskIndex > 1) {
|
|
58
|
+
// If not the first task, add results from previous task
|
|
59
|
+
args.push(...results);
|
|
60
|
+
}
|
|
61
|
+
args.push(runNextTask);
|
|
62
|
+
task.fn.apply(null, args);
|
|
63
|
+
} else {
|
|
64
|
+
return finalCallback(new Error('Invalid task in waterfall'));
|
|
65
|
+
}
|
|
66
|
+
} catch (error) {
|
|
67
|
+
return finalCallback(error);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
runNextTask();
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Apply partial application to a function (like async.apply)
|
|
76
|
+
*/
|
|
77
|
+
function apply(fn, ...args) {
|
|
78
|
+
return {
|
|
79
|
+
__async_apply: true,
|
|
80
|
+
fn: fn,
|
|
81
|
+
args: args
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Execute a function for each item in an array, in series
|
|
87
|
+
*/
|
|
88
|
+
function eachSeries(array, iterator, callback) {
|
|
89
|
+
callback = callback || function() {};
|
|
90
|
+
|
|
91
|
+
let index = 0;
|
|
92
|
+
|
|
93
|
+
function processNext(err) {
|
|
94
|
+
if (err) {
|
|
95
|
+
return callback(err);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (index >= array.length) {
|
|
99
|
+
return callback(null);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const item = array[index++];
|
|
103
|
+
|
|
104
|
+
try {
|
|
105
|
+
iterator(item, processNext);
|
|
106
|
+
} catch (error) {
|
|
107
|
+
return callback(error);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
processNext();
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Execute a function for each item in an array, in parallel
|
|
116
|
+
*/
|
|
117
|
+
function each(array, iterator, callback) {
|
|
118
|
+
callback = callback || function() {};
|
|
119
|
+
|
|
120
|
+
if (array.length === 0) {
|
|
121
|
+
return callback(null);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
let completed = 0;
|
|
125
|
+
let hasError = false;
|
|
126
|
+
|
|
127
|
+
array.forEach(function(item) {
|
|
128
|
+
iterator(item, function(err) {
|
|
129
|
+
if (hasError) return;
|
|
130
|
+
|
|
131
|
+
if (err) {
|
|
132
|
+
hasError = true;
|
|
133
|
+
return callback(err);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
completed++;
|
|
137
|
+
if (completed === array.length) {
|
|
138
|
+
callback(null);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Execute a function repeatedly while a condition is true
|
|
146
|
+
*/
|
|
147
|
+
function whilst(test, iterator, callback) {
|
|
148
|
+
callback = callback || function() {};
|
|
149
|
+
|
|
150
|
+
function iterate(err) {
|
|
151
|
+
if (err) {
|
|
152
|
+
return callback(err);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!test()) {
|
|
156
|
+
return callback(null);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
iterator(iterate);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
return callback(error);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
iterate();
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Task queue implementation
|
|
171
|
+
*/
|
|
172
|
+
function queue(worker, concurrency = 1) {
|
|
173
|
+
const tasks = [];
|
|
174
|
+
let running = 0;
|
|
175
|
+
let paused = false;
|
|
176
|
+
|
|
177
|
+
function processTasks() {
|
|
178
|
+
while (tasks.length > 0 && running < concurrency && !paused) {
|
|
179
|
+
const task = tasks.shift();
|
|
180
|
+
running++;
|
|
181
|
+
|
|
182
|
+
worker(task.data, function(err) {
|
|
183
|
+
running--;
|
|
184
|
+
task.callback(err);
|
|
185
|
+
|
|
186
|
+
// Process next tasks
|
|
187
|
+
if (tasks.length > 0) {
|
|
188
|
+
setImmediate(processTasks);
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
push: function(data, callback) {
|
|
196
|
+
callback = callback || function() {};
|
|
197
|
+
tasks.push({ data, callback });
|
|
198
|
+
setImmediate(processTasks);
|
|
199
|
+
},
|
|
200
|
+
|
|
201
|
+
unshift: function(data, callback) {
|
|
202
|
+
callback = callback || function() {};
|
|
203
|
+
tasks.unshift({ data, callback });
|
|
204
|
+
setImmediate(processTasks);
|
|
205
|
+
},
|
|
206
|
+
|
|
207
|
+
pause: function() {
|
|
208
|
+
paused = true;
|
|
209
|
+
},
|
|
210
|
+
|
|
211
|
+
resume: function() {
|
|
212
|
+
paused = false;
|
|
213
|
+
setImmediate(processTasks);
|
|
214
|
+
},
|
|
215
|
+
|
|
216
|
+
length: function() {
|
|
217
|
+
return tasks.length;
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
running: function() {
|
|
221
|
+
return running;
|
|
222
|
+
},
|
|
223
|
+
|
|
224
|
+
idle: function() {
|
|
225
|
+
return tasks.length === 0 && running === 0;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
module.exports = {
|
|
231
|
+
waterfall,
|
|
232
|
+
apply,
|
|
233
|
+
eachSeries,
|
|
234
|
+
each,
|
|
235
|
+
whilst,
|
|
236
|
+
queue
|
|
237
|
+
};
|
package/lib/cursor.js
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Manage access to data, be it to find, update or remove it
|
|
3
|
+
*/
|
|
4
|
+
var model = require('./model')
|
|
5
|
+
, _ = require('./underscore')
|
|
6
|
+
;
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Create a new cursor for this collection
|
|
12
|
+
* @param {Datastore} db - The datastore this cursor is bound to
|
|
13
|
+
* @param {Query} query - The query this cursor will operate on
|
|
14
|
+
* @param {Function} execFn - Handler to be executed after cursor has found the results and before the callback passed to find/findOne/update/remove
|
|
15
|
+
*/
|
|
16
|
+
function Cursor (db, query, execFn) {
|
|
17
|
+
this.db = db;
|
|
18
|
+
this.query = query || {};
|
|
19
|
+
if (execFn) { this.execFn = execFn; }
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Set a limit to the number of results
|
|
25
|
+
*/
|
|
26
|
+
Cursor.prototype.limit = function(limit) {
|
|
27
|
+
this._limit = limit;
|
|
28
|
+
return this;
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Skip a the number of results
|
|
34
|
+
*/
|
|
35
|
+
Cursor.prototype.skip = function(skip) {
|
|
36
|
+
this._skip = skip;
|
|
37
|
+
return this;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Sort results of the query
|
|
43
|
+
* @param {SortQuery} sortQuery - SortQuery is { field: order }, field can use the dot-notation, order is 1 for ascending and -1 for descending
|
|
44
|
+
*/
|
|
45
|
+
Cursor.prototype.sort = function(sortQuery) {
|
|
46
|
+
this._sort = sortQuery;
|
|
47
|
+
return this;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Add the use of a projection
|
|
53
|
+
* @param {Object} projection - MongoDB-style projection. {} means take all fields. Then it's { key1: 1, key2: 1 } to take only key1 and key2
|
|
54
|
+
* { key1: 0, key2: 0 } to omit only key1 and key2. Except _id, you can't mix takes and omits
|
|
55
|
+
*/
|
|
56
|
+
Cursor.prototype.projection = function(projection) {
|
|
57
|
+
this._projection = projection;
|
|
58
|
+
return this;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Apply the projection
|
|
64
|
+
*/
|
|
65
|
+
Cursor.prototype.project = function (candidates) {
|
|
66
|
+
var res = [], self = this
|
|
67
|
+
, keepId, action, keys
|
|
68
|
+
;
|
|
69
|
+
|
|
70
|
+
if (this._projection === undefined || Object.keys(this._projection).length === 0) {
|
|
71
|
+
return candidates;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
keepId = this._projection._id === 0 ? false : true;
|
|
75
|
+
this._projection = _.omit(this._projection, '_id');
|
|
76
|
+
|
|
77
|
+
// Check for consistency
|
|
78
|
+
keys = Object.keys(this._projection);
|
|
79
|
+
keys.forEach(function (k) {
|
|
80
|
+
if (action !== undefined && self._projection[k] !== action) { throw new Error("Can't both keep and omit fields except for _id"); }
|
|
81
|
+
action = self._projection[k];
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// Do the actual projection
|
|
85
|
+
candidates.forEach(function (candidate) {
|
|
86
|
+
var toPush;
|
|
87
|
+
if (action === 1) { // pick-type projection
|
|
88
|
+
toPush = { $set: {} };
|
|
89
|
+
keys.forEach(function (k) {
|
|
90
|
+
toPush.$set[k] = model.getDotValue(candidate, k);
|
|
91
|
+
if (toPush.$set[k] === undefined) { delete toPush.$set[k]; }
|
|
92
|
+
});
|
|
93
|
+
toPush = model.modify({}, toPush);
|
|
94
|
+
} else { // omit-type projection
|
|
95
|
+
toPush = { $unset: {} };
|
|
96
|
+
keys.forEach(function (k) { toPush.$unset[k] = true });
|
|
97
|
+
toPush = model.modify(candidate, toPush);
|
|
98
|
+
}
|
|
99
|
+
if (keepId) {
|
|
100
|
+
toPush._id = candidate._id;
|
|
101
|
+
} else {
|
|
102
|
+
delete toPush._id;
|
|
103
|
+
}
|
|
104
|
+
res.push(toPush);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
return res;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Get all matching elements
|
|
113
|
+
* Will return pointers to matched elements (shallow copies), returning full copies is the role of find or findOne
|
|
114
|
+
* This is an internal function, use exec which uses the executor
|
|
115
|
+
*
|
|
116
|
+
* @param {Function} callback - Signature: err, results
|
|
117
|
+
*/
|
|
118
|
+
Cursor.prototype._exec = function(_callback) {
|
|
119
|
+
var res = [], added = 0, skipped = 0, self = this
|
|
120
|
+
, error = null
|
|
121
|
+
, i, keys, key
|
|
122
|
+
;
|
|
123
|
+
|
|
124
|
+
function callback (error, res) {
|
|
125
|
+
if (self.execFn) {
|
|
126
|
+
return self.execFn(error, res, _callback);
|
|
127
|
+
} else {
|
|
128
|
+
return _callback(error, res);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.db.getCandidates(this.query, function (err, candidates) {
|
|
133
|
+
if (err) { return callback(err); }
|
|
134
|
+
|
|
135
|
+
try {
|
|
136
|
+
for (i = 0; i < candidates.length; i += 1) {
|
|
137
|
+
if (model.match(candidates[i], self.query)) {
|
|
138
|
+
// If a sort is defined, wait for the results to be sorted before applying limit and skip
|
|
139
|
+
if (!self._sort) {
|
|
140
|
+
if (self._skip && self._skip > skipped) {
|
|
141
|
+
skipped += 1;
|
|
142
|
+
} else {
|
|
143
|
+
res.push(candidates[i]);
|
|
144
|
+
added += 1;
|
|
145
|
+
if (self._limit && self._limit <= added) { break; }
|
|
146
|
+
}
|
|
147
|
+
} else {
|
|
148
|
+
res.push(candidates[i]);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
} catch (err) {
|
|
153
|
+
return callback(err);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Apply all sorts
|
|
157
|
+
if (self._sort) {
|
|
158
|
+
keys = Object.keys(self._sort);
|
|
159
|
+
|
|
160
|
+
// Sorting
|
|
161
|
+
var criteria = [];
|
|
162
|
+
for (i = 0; i < keys.length; i++) {
|
|
163
|
+
key = keys[i];
|
|
164
|
+
criteria.push({ key: key, direction: self._sort[key] });
|
|
165
|
+
}
|
|
166
|
+
res.sort(function(a, b) {
|
|
167
|
+
var criterion, compare, i;
|
|
168
|
+
for (i = 0; i < criteria.length; i++) {
|
|
169
|
+
criterion = criteria[i];
|
|
170
|
+
compare = criterion.direction * model.compareThings(model.getDotValue(a, criterion.key), model.getDotValue(b, criterion.key), self.db.compareStrings);
|
|
171
|
+
if (compare !== 0) {
|
|
172
|
+
return compare;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return 0;
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
// Applying limit and skip
|
|
179
|
+
var limit = self._limit || res.length
|
|
180
|
+
, skip = self._skip || 0;
|
|
181
|
+
|
|
182
|
+
res = res.slice(skip, skip + limit);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Apply projection
|
|
186
|
+
try {
|
|
187
|
+
res = self.project(res);
|
|
188
|
+
} catch (e) {
|
|
189
|
+
error = e;
|
|
190
|
+
res = undefined;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return callback(error, res);
|
|
194
|
+
});
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
Cursor.prototype.exec = function () {
|
|
198
|
+
this.db.executor.push({ this: this, fn: this._exec, arguments: arguments });
|
|
199
|
+
};
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
// Interface
|
|
204
|
+
module.exports = Cursor;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
var crypto = require('crypto')
|
|
2
|
+
;
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Return a random alphanumerical string of length len
|
|
6
|
+
* There is a very small probability (less than 1/1,000,000) for the length to be less than len
|
|
7
|
+
* (il the base64 conversion yields too many pluses and slashes) but
|
|
8
|
+
* that's not an issue here
|
|
9
|
+
* The probability of a collision is extremely small (need 3*10^12 documents to have one chance in a million of a collision)
|
|
10
|
+
* See http://en.wikipedia.org/wiki/Birthday_problem
|
|
11
|
+
*/
|
|
12
|
+
function uid (len) {
|
|
13
|
+
return crypto.randomBytes(Math.ceil(Math.max(8, len * 2)))
|
|
14
|
+
.toString('base64')
|
|
15
|
+
.replace(/[+\/]/g, '')
|
|
16
|
+
.slice(0, len);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
// Interface
|
|
21
|
+
module.exports.uid = uid;
|
|
22
|
+
|