ueberdb2 2.1.1 → 2.2.3
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/.github/workflows/npmpublish.yml +1 -0
- package/CHANGELOG.md +20 -0
- package/README.md +70 -92
- package/databases/couch_db.js +85 -144
- package/index.js +64 -84
- package/lib/AbstractDatabase.js +5 -0
- package/lib/CacheAndBufferLayer.js +95 -40
- package/lib/logging.js +29 -0
- package/package.json +1 -1
- package/test/lib/databases.js +3 -2
- package/test/test.js +92 -75
- package/test/test_bulk.js +1 -3
- package/test/test_findKeys.js +43 -0
- package/test/test_flush.js +61 -0
- package/test/test_metrics.js +1 -5
- package/test/test_postgres.js +3 -4
- package/test/test_setSub.js +3 -4
- package/test/test_tojson.js +1 -4
package/index.js
CHANGED
|
@@ -18,24 +18,12 @@
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
const cacheAndBufferLayer = require('./lib/CacheAndBufferLayer');
|
|
21
|
+
const logging = require('./lib/logging');
|
|
21
22
|
const util = require('util');
|
|
22
23
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
const
|
|
26
|
-
const logLevelsUsed = ['debug', 'error'];
|
|
27
|
-
logger = Object.create(logger || {});
|
|
28
|
-
for (const level of logLevelsUsed) {
|
|
29
|
-
const enabledFnName = `is${level.charAt(0).toUpperCase() + level.slice(1)}Enabled`;
|
|
30
|
-
if (typeof logger[level] !== 'function') {
|
|
31
|
-
logger[level] = () => {};
|
|
32
|
-
logger[enabledFnName] = () => false;
|
|
33
|
-
} else if (typeof logger[enabledFnName] !== 'function') {
|
|
34
|
-
logger[enabledFnName] = () => true;
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
return logger;
|
|
38
|
-
};
|
|
24
|
+
const cbDb = {};
|
|
25
|
+
const fns = ['close', 'findKeys', 'flush', 'get', 'getSub', 'init', 'remove', 'set', 'setSub'];
|
|
26
|
+
for (const fn of fns) cbDb[fn] = util.callbackify(cacheAndBufferLayer.Database.prototype[fn]);
|
|
39
27
|
|
|
40
28
|
const makeDoneCallback = (callback, deprecated) => (err) => {
|
|
41
29
|
if (callback) callback(err);
|
|
@@ -62,8 +50,9 @@ exports.Database = class {
|
|
|
62
50
|
this.dbModule = require(`./databases/${type}_db`);
|
|
63
51
|
this.dbSettings = dbSettings;
|
|
64
52
|
this.wrapperSettings = wrapperSettings;
|
|
65
|
-
this.logger = normalizeLogger(logger);
|
|
53
|
+
this.logger = logging.normalizeLogger(logger);
|
|
66
54
|
const db = new this.dbModule.Database(this.dbSettings);
|
|
55
|
+
db.logger = this.logger;
|
|
67
56
|
this.db = new cacheAndBufferLayer.Database(db, this.wrapperSettings, this.logger);
|
|
68
57
|
|
|
69
58
|
// Expose the cache wrapper's metrics to the user. See lib/CacheAndBufferLayer.js for details.
|
|
@@ -73,12 +62,12 @@ exports.Database = class {
|
|
|
73
62
|
this.metrics = this.db.metrics;
|
|
74
63
|
}
|
|
75
64
|
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
65
|
+
/**
|
|
66
|
+
* @param callback - Deprecated. Node-style callback. If null, a Promise is returned.
|
|
67
|
+
*/
|
|
68
|
+
init(callback = null) {
|
|
69
|
+
if (callback != null) return cbDb.init.call(this.db, callback);
|
|
70
|
+
return this.db.init();
|
|
82
71
|
}
|
|
83
72
|
|
|
84
73
|
/**
|
|
@@ -87,104 +76,95 @@ exports.Database = class {
|
|
|
87
76
|
|
|
88
77
|
/**
|
|
89
78
|
* Deprecated synonym of flush().
|
|
79
|
+
*
|
|
80
|
+
* @param callback - Deprecated. Node-style callback. If null, a Promise is returned.
|
|
90
81
|
*/
|
|
91
|
-
doShutdown(callback) {
|
|
92
|
-
this.flush(callback);
|
|
82
|
+
doShutdown(callback = null) {
|
|
83
|
+
return this.flush(callback);
|
|
93
84
|
}
|
|
94
85
|
|
|
95
86
|
/**
|
|
96
87
|
* Writes any unsaved changes to the underlying database.
|
|
88
|
+
*
|
|
89
|
+
* @param callback - Deprecated. Node-style callback. If null, a Promise is returned.
|
|
97
90
|
*/
|
|
98
|
-
flush(callback) {
|
|
99
|
-
|
|
91
|
+
flush(callback = null) {
|
|
92
|
+
if (callback != null) return cbDb.flush.call(this.db, callback);
|
|
93
|
+
return this.db.flush();
|
|
100
94
|
}
|
|
101
95
|
|
|
102
|
-
|
|
103
|
-
|
|
96
|
+
/**
|
|
97
|
+
* @param callback - Deprecated. Node-style callback. If null, a Promise is returned.
|
|
98
|
+
*/
|
|
99
|
+
get(key, callback = null) {
|
|
100
|
+
if (callback != null) return cbDb.get.call(this.db, key, callback);
|
|
101
|
+
return this.db.get(key);
|
|
104
102
|
}
|
|
105
103
|
|
|
106
|
-
|
|
107
|
-
|
|
104
|
+
/**
|
|
105
|
+
* @param callback - Deprecated. Node-style callback. If null, a Promise is returned.
|
|
106
|
+
*/
|
|
107
|
+
findKeys(key, notKey, callback = null) {
|
|
108
|
+
if (callback != null) return cbDb.findKeys.call(this.db, key, notKey, callback);
|
|
109
|
+
return this.db.findKeys(key, notKey);
|
|
108
110
|
}
|
|
109
111
|
|
|
110
112
|
/**
|
|
111
113
|
* Removes an entry from the database if present.
|
|
112
114
|
*
|
|
113
|
-
* @param cb Called when the write has been committed to the
|
|
114
|
-
*
|
|
115
|
+
* @param cb Deprecated. Node-style callback. Called when the write has been committed to the
|
|
116
|
+
* underlying database driver. If null, a Promise is returned.
|
|
117
|
+
* @param deprecated Deprecated callback that is called just after cb. Ignored if cb is null.
|
|
115
118
|
*/
|
|
116
|
-
remove(key, cb, deprecated = null) {
|
|
117
|
-
|
|
119
|
+
remove(key, cb = null, deprecated = null) {
|
|
120
|
+
if (cb != null) return cbDb.remove.call(this.db, key, makeDoneCallback(cb, deprecated));
|
|
121
|
+
return this.db.remove(key);
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
/**
|
|
121
125
|
* Adds or changes the value of an entry.
|
|
122
126
|
*
|
|
123
|
-
* @param cb Called when the write has been committed to the
|
|
124
|
-
*
|
|
127
|
+
* @param cb Deprecated. Node-style callback. Called when the write has been committed to the
|
|
128
|
+
* underlying database driver. If null, a Promise is returned.
|
|
129
|
+
* @param deprecated Deprecated callback that is called just after cb. Ignored if cb is null.
|
|
125
130
|
*/
|
|
126
|
-
set(key, value, cb, deprecated = null) {
|
|
127
|
-
|
|
128
|
-
|
|
131
|
+
set(key, value, cb = null, deprecated = null) {
|
|
132
|
+
if (cb != null) return cbDb.set.call(this.db, key, value, makeDoneCallback(cb, deprecated));
|
|
133
|
+
return this.db.set(key, value);
|
|
129
134
|
}
|
|
130
135
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
136
|
+
/**
|
|
137
|
+
* @param callback - Deprecated. Node-style callback. If null, a Promise is returned.
|
|
138
|
+
*/
|
|
139
|
+
getSub(key, sub, callback = null) {
|
|
140
|
+
if (callback != null) return cbDb.getSub.call(this.db, key, sub, callback);
|
|
141
|
+
return this.db.getSub(key, sub);
|
|
134
142
|
}
|
|
135
143
|
|
|
136
144
|
/**
|
|
137
145
|
* Adds or changes a subvalue of an entry.
|
|
138
146
|
*
|
|
139
|
-
* @param cb Called when the write has been committed to the
|
|
140
|
-
*
|
|
147
|
+
* @param cb Deprecated. Node-style callback. Called when the write has been committed to the
|
|
148
|
+
* underlying database driver. If null, a Promise is returned.
|
|
149
|
+
* @param deprecated Deprecated callback that is called just after cb. Ignored if cb is null.
|
|
141
150
|
*/
|
|
142
|
-
setSub(key, sub, value, cb, deprecated = null) {
|
|
143
|
-
|
|
144
|
-
|
|
151
|
+
setSub(key, sub, value, cb = null, deprecated = null) {
|
|
152
|
+
if (cb != null) {
|
|
153
|
+
return cbDb.setSub.call(this.db, key, sub, value, makeDoneCallback(cb, deprecated));
|
|
154
|
+
}
|
|
155
|
+
return this.db.setSub(key, sub, value);
|
|
145
156
|
}
|
|
146
157
|
|
|
147
158
|
/**
|
|
148
159
|
* Flushes unwritten changes then closes the connection to the underlying database. After this
|
|
149
160
|
* returns, any future call to a method on this object may result in an error.
|
|
161
|
+
*
|
|
162
|
+
* @param callback - Deprecated. Node-style callback. If null, a Promise is returned.
|
|
150
163
|
*/
|
|
151
|
-
close(callback) {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
};
|
|
155
|
-
|
|
156
|
-
const clone = (obj, key = '') => {
|
|
157
|
-
// Handle the 3 simple types, and null or undefined
|
|
158
|
-
if (null == obj || 'object' !== typeof obj) return obj;
|
|
159
|
-
|
|
160
|
-
if (typeof obj.toJSON === 'function') return clone(obj.toJSON(key));
|
|
161
|
-
|
|
162
|
-
// Handle Date
|
|
163
|
-
if (obj instanceof Date) {
|
|
164
|
-
const copy = new Date();
|
|
165
|
-
copy.setTime(obj.getTime());
|
|
166
|
-
return copy;
|
|
164
|
+
close(callback = null) {
|
|
165
|
+
if (callback != null) return cbDb.close.call(this.db, callback);
|
|
166
|
+
return this.db.close();
|
|
167
167
|
}
|
|
168
|
-
|
|
169
|
-
// Handle Array
|
|
170
|
-
if (obj instanceof Array) {
|
|
171
|
-
const copy = [];
|
|
172
|
-
for (let i = 0, len = obj.length; i < len; ++i) {
|
|
173
|
-
copy[i] = clone(obj[i], String(i));
|
|
174
|
-
}
|
|
175
|
-
return copy;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
// Handle Object
|
|
179
|
-
if (obj instanceof Object) {
|
|
180
|
-
const copy = {};
|
|
181
|
-
for (const attr in obj) {
|
|
182
|
-
if (Object.prototype.hasOwnProperty.call(obj, attr)) copy[attr] = clone(obj[attr], attr);
|
|
183
|
-
}
|
|
184
|
-
return copy;
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
throw new Error("Unable to copy obj! Its type isn't supported.");
|
|
188
168
|
};
|
|
189
169
|
|
|
190
170
|
/**
|
package/lib/AbstractDatabase.js
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
+
const logging = require('./logging');
|
|
4
|
+
|
|
5
|
+
const nullLogger = logging.normalizeLogger(null);
|
|
6
|
+
|
|
3
7
|
module.exports = class AbstractDatabase {
|
|
4
8
|
constructor() {
|
|
5
9
|
if (new.target === module.exports) {
|
|
@@ -8,6 +12,7 @@ module.exports = class AbstractDatabase {
|
|
|
8
12
|
for (const fn of ['init', 'close', 'get', 'findKeys', 'remove', 'set']) {
|
|
9
13
|
if (typeof this[fn] !== 'function') throw new TypeError(`method ${fn} not defined`);
|
|
10
14
|
}
|
|
15
|
+
this.logger = nullLogger;
|
|
11
16
|
}
|
|
12
17
|
|
|
13
18
|
/**
|
|
@@ -165,6 +165,11 @@ exports.Database = class {
|
|
|
165
165
|
// underlying database and we are awaiting commit.
|
|
166
166
|
this.buffer = new LRU(this.settings.cache, (k, v) => !v.dirty && !v.writingInProgress);
|
|
167
167
|
|
|
168
|
+
// Either null if flushing is currently allowed, or a Promise that will resolve when it is OK to
|
|
169
|
+
// start flushing. The Promise has a `count` property that tracks the number of operations that
|
|
170
|
+
// are currently preventing flush() from running.
|
|
171
|
+
this._flushPaused = null;
|
|
172
|
+
|
|
168
173
|
// Maps database key to a Promise that is resolved when the record is unlocked.
|
|
169
174
|
this._locks = new Map();
|
|
170
175
|
|
|
@@ -245,6 +250,33 @@ exports.Database = class {
|
|
|
245
250
|
this._locks.delete(key);
|
|
246
251
|
}
|
|
247
252
|
|
|
253
|
+
// Block flush() until _resumeFlush() is called. This is needed so that a call to flush() after a
|
|
254
|
+
// write (set(), setSub(), or remove() call) in the same ECMAScript macro- or microtask will see
|
|
255
|
+
// the enqueued write and flush it.
|
|
256
|
+
//
|
|
257
|
+
// An alternative would be to change flush() to schedule its actions in a future microtask after
|
|
258
|
+
// the write has been queued in the buffer, but:
|
|
259
|
+
//
|
|
260
|
+
// * That would be fragile: Every use of await moves the subsequent processing to a new
|
|
261
|
+
// microtask, so flush() would need to do a number of `await Promise.resolve();` calls equal
|
|
262
|
+
// to the number of awaits before a write is actually buffered.
|
|
263
|
+
//
|
|
264
|
+
// * It won't work for setSub() because it must wait for a read to complete before it buffers
|
|
265
|
+
// the write.
|
|
266
|
+
_pauseFlush() {
|
|
267
|
+
if (this._flushPaused == null) {
|
|
268
|
+
this._flushPaused = new SelfContainedPromise();
|
|
269
|
+
this._flushPaused.count = 0;
|
|
270
|
+
}
|
|
271
|
+
++this._flushPaused.count;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
_resumeFlush() {
|
|
275
|
+
if (--this._flushPaused.count > 0) return;
|
|
276
|
+
this._flushPaused.done();
|
|
277
|
+
this._flushPaused = null;
|
|
278
|
+
}
|
|
279
|
+
|
|
248
280
|
/**
|
|
249
281
|
* wraps the init function of the original DB
|
|
250
282
|
*/
|
|
@@ -266,12 +298,14 @@ exports.Database = class {
|
|
|
266
298
|
* Gets the value trough the wrapper.
|
|
267
299
|
*/
|
|
268
300
|
async get(key) {
|
|
301
|
+
let v;
|
|
269
302
|
await this._lock(key);
|
|
270
303
|
try {
|
|
271
|
-
|
|
304
|
+
v = await this._getLocked(key);
|
|
272
305
|
} finally {
|
|
273
306
|
this._unlock(key);
|
|
274
307
|
}
|
|
308
|
+
return clone(v);
|
|
275
309
|
}
|
|
276
310
|
|
|
277
311
|
async _getLocked(key) {
|
|
@@ -334,12 +368,13 @@ exports.Database = class {
|
|
|
334
368
|
* returns the key entries via callback.
|
|
335
369
|
*/
|
|
336
370
|
async findKeys(key, notKey) {
|
|
337
|
-
|
|
371
|
+
await this.flush();
|
|
338
372
|
const keyValues = await this.wrappedDB.findKeys(key, notKey);
|
|
339
373
|
if (this.logger.isDebugEnabled()) {
|
|
340
|
-
this.logger.debug(
|
|
374
|
+
this.logger.debug(
|
|
375
|
+
`GET - ${key}-${notKey} - ${JSON.stringify(keyValues)} - from database `);
|
|
341
376
|
}
|
|
342
|
-
return keyValues;
|
|
377
|
+
return clone(keyValues);
|
|
343
378
|
}
|
|
344
379
|
|
|
345
380
|
/**
|
|
@@ -354,9 +389,19 @@ exports.Database = class {
|
|
|
354
389
|
* Sets the value trough the wrapper
|
|
355
390
|
*/
|
|
356
391
|
async set(key, value) {
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
this.
|
|
392
|
+
value = clone(value);
|
|
393
|
+
let p;
|
|
394
|
+
this._pauseFlush();
|
|
395
|
+
try {
|
|
396
|
+
await this._lock(key);
|
|
397
|
+
try {
|
|
398
|
+
p = this._setLocked(key, value);
|
|
399
|
+
} finally {
|
|
400
|
+
this._unlock(key);
|
|
401
|
+
}
|
|
402
|
+
} finally {
|
|
403
|
+
this._resumeFlush();
|
|
404
|
+
}
|
|
360
405
|
await p;
|
|
361
406
|
}
|
|
362
407
|
|
|
@@ -406,47 +451,53 @@ exports.Database = class {
|
|
|
406
451
|
* Sets a subvalue
|
|
407
452
|
*/
|
|
408
453
|
async setSub(key, sub, value) {
|
|
454
|
+
value = clone(value);
|
|
409
455
|
if (this.logger.isDebugEnabled()) {
|
|
410
456
|
this.logger.debug(`SETSUB - ${key}${JSON.stringify(sub)} - ${JSON.stringify(value)}`);
|
|
411
457
|
}
|
|
412
458
|
let p;
|
|
413
|
-
|
|
459
|
+
this._pauseFlush();
|
|
414
460
|
try {
|
|
415
|
-
|
|
461
|
+
await this._lock(key);
|
|
416
462
|
try {
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
463
|
+
let base;
|
|
464
|
+
try {
|
|
465
|
+
const fullValue = await this._getLocked(key);
|
|
466
|
+
base = {fullValue};
|
|
467
|
+
// Emulate a pointer to the property that should be set to `value`.
|
|
468
|
+
const ptr = {obj: base, prop: 'fullValue'};
|
|
469
|
+
for (let i = 0; i < sub.length; i++) {
|
|
470
|
+
if (sub[i] === '__proto__') {
|
|
471
|
+
throw new Error('Modifying object prototype is not supported for security reasons');
|
|
472
|
+
}
|
|
473
|
+
let o = ptr.obj[ptr.prop];
|
|
474
|
+
if (o == null) ptr.obj[ptr.prop] = o = {};
|
|
475
|
+
// If o is a primitive (string, number, etc.), then setting `o.foo` has no effect
|
|
476
|
+
// because ECMAScript automatically wraps primitives in a temporary wrapper object.
|
|
477
|
+
if (typeof o !== 'object') {
|
|
478
|
+
throw new TypeError(
|
|
479
|
+
`Cannot set property ${JSON.stringify(sub[i])} on non-object ` +
|
|
432
480
|
`${JSON.stringify(o)} (key: ${JSON.stringify(key)} ` +
|
|
433
481
|
`value in db: ${JSON.stringify(fullValue)} ` +
|
|
434
482
|
`sub: ${JSON.stringify(sub.slice(0, i + 1))})`);
|
|
483
|
+
}
|
|
484
|
+
ptr.obj = ptr.obj[ptr.prop];
|
|
485
|
+
ptr.prop = sub[i];
|
|
435
486
|
}
|
|
436
|
-
ptr.obj
|
|
437
|
-
|
|
487
|
+
ptr.obj[ptr.prop] = value;
|
|
488
|
+
} catch (err) {
|
|
489
|
+
// this._setLocked() will not be called but it should still count as a write failure.
|
|
490
|
+
++this.metrics.writes;
|
|
491
|
+
++this.metrics.writesFailed;
|
|
492
|
+
++this.metrics.writesFinished;
|
|
493
|
+
throw err;
|
|
438
494
|
}
|
|
439
|
-
|
|
440
|
-
}
|
|
441
|
-
|
|
442
|
-
++this.metrics.writes;
|
|
443
|
-
++this.metrics.writesFailed;
|
|
444
|
-
++this.metrics.writesFinished;
|
|
445
|
-
throw err;
|
|
495
|
+
p = this._setLocked(key, base.fullValue);
|
|
496
|
+
} finally {
|
|
497
|
+
this._unlock(key);
|
|
446
498
|
}
|
|
447
|
-
p = this._setLocked(key, base.fullValue);
|
|
448
499
|
} finally {
|
|
449
|
-
this.
|
|
500
|
+
this._resumeFlush();
|
|
450
501
|
}
|
|
451
502
|
await p;
|
|
452
503
|
}
|
|
@@ -457,13 +508,14 @@ exports.Database = class {
|
|
|
457
508
|
* "bla"]
|
|
458
509
|
*/
|
|
459
510
|
async getSub(key, sub) {
|
|
511
|
+
let subvalue;
|
|
460
512
|
await this._lock(key);
|
|
461
513
|
try {
|
|
462
514
|
// get the full value
|
|
463
515
|
const value = await this._getLocked(key);
|
|
464
516
|
|
|
465
517
|
// everything is correct, navigate to the subvalue and return it
|
|
466
|
-
|
|
518
|
+
subvalue = value;
|
|
467
519
|
|
|
468
520
|
for (let i = 0; i < sub.length; i++) {
|
|
469
521
|
// test if the subvalue exist
|
|
@@ -479,10 +531,10 @@ exports.Database = class {
|
|
|
479
531
|
if (this.logger.isDebugEnabled()) {
|
|
480
532
|
this.logger.debug(`GETSUB - ${key}${JSON.stringify(sub)} - ${JSON.stringify(subvalue)}`);
|
|
481
533
|
}
|
|
482
|
-
return subvalue;
|
|
483
534
|
} finally {
|
|
484
535
|
this._unlock(key);
|
|
485
536
|
}
|
|
537
|
+
return clone(subvalue);
|
|
486
538
|
}
|
|
487
539
|
|
|
488
540
|
/**
|
|
@@ -492,6 +544,7 @@ exports.Database = class {
|
|
|
492
544
|
if (this._flushDone == null) {
|
|
493
545
|
this._flushDone = (async () => {
|
|
494
546
|
while (true) {
|
|
547
|
+
while (this._flushPaused != null) await this._flushPaused;
|
|
495
548
|
const dirtyEntries = [];
|
|
496
549
|
for (const entry of this.buffer) {
|
|
497
550
|
if (entry[1].dirty && !entry[1].writingInProgress) {
|
|
@@ -576,10 +629,12 @@ exports.Database = class {
|
|
|
576
629
|
}
|
|
577
630
|
};
|
|
578
631
|
|
|
579
|
-
const clone = (obj) => {
|
|
632
|
+
const clone = (obj, key = '') => {
|
|
580
633
|
// Handle the 3 simple types, and null or undefined
|
|
581
634
|
if (null == obj || 'object' !== typeof obj) return obj;
|
|
582
635
|
|
|
636
|
+
if (typeof obj.toJSON === 'function') return clone(obj.toJSON(key));
|
|
637
|
+
|
|
583
638
|
// Handle Date
|
|
584
639
|
if (obj instanceof Date) {
|
|
585
640
|
const copy = new Date();
|
|
@@ -591,7 +646,7 @@ const clone = (obj) => {
|
|
|
591
646
|
if (obj instanceof Array) {
|
|
592
647
|
const copy = [];
|
|
593
648
|
for (let i = 0, len = obj.length; i < len; ++i) {
|
|
594
|
-
copy[i] = clone(obj[i]);
|
|
649
|
+
copy[i] = clone(obj[i], String(i));
|
|
595
650
|
}
|
|
596
651
|
return copy;
|
|
597
652
|
}
|
|
@@ -600,7 +655,7 @@ const clone = (obj) => {
|
|
|
600
655
|
if (obj instanceof Object) {
|
|
601
656
|
const copy = {};
|
|
602
657
|
for (const attr in obj) {
|
|
603
|
-
if (Object.prototype.hasOwnProperty.call(obj, attr)) copy[attr] = clone(obj[attr]);
|
|
658
|
+
if (Object.prototype.hasOwnProperty.call(obj, attr)) copy[attr] = clone(obj[attr], attr);
|
|
604
659
|
}
|
|
605
660
|
return copy;
|
|
606
661
|
}
|
package/lib/logging.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const {Console} = require('console');
|
|
4
|
+
const {stdout, stderr} = require('process');
|
|
5
|
+
|
|
6
|
+
class ConsoleLogger extends Console {
|
|
7
|
+
constructor(opts = {}) { super({stdout, stderr, inspectOptions: {depth: Infinity}, ...opts}); }
|
|
8
|
+
isDebugEnabled() { return false; }
|
|
9
|
+
isInfoEnabled() { return true; }
|
|
10
|
+
isWarnEnabled() { return true; }
|
|
11
|
+
isErrorEnabled() { return true; }
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
exports.ConsoleLogger = ConsoleLogger;
|
|
15
|
+
|
|
16
|
+
exports.normalizeLogger = (logger) => {
|
|
17
|
+
const logLevelsUsed = ['debug', 'info', 'warn', 'error'];
|
|
18
|
+
logger = Object.create(logger || {});
|
|
19
|
+
for (const level of logLevelsUsed) {
|
|
20
|
+
const enabledFnName = `is${level.charAt(0).toUpperCase() + level.slice(1)}Enabled`;
|
|
21
|
+
if (typeof logger[level] !== 'function') {
|
|
22
|
+
logger[level] = () => {};
|
|
23
|
+
logger[enabledFnName] = () => false;
|
|
24
|
+
} else if (typeof logger[enabledFnName] !== 'function') {
|
|
25
|
+
logger[enabledFnName] = () => true;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
return logger;
|
|
29
|
+
};
|
package/package.json
CHANGED
package/test/lib/databases.js
CHANGED
|
@@ -51,13 +51,14 @@ exports.databases = {
|
|
|
51
51
|
removeMax: 0.3,
|
|
52
52
|
},
|
|
53
53
|
},
|
|
54
|
-
/* Disabled due to "Document update conflict" error in findKeys()
|
|
55
54
|
couch: {
|
|
56
55
|
host: '127.0.0.1',
|
|
57
56
|
port: 5984,
|
|
58
57
|
database: 'ueberdb',
|
|
59
58
|
user: 'ueberdb',
|
|
60
59
|
password: 'ueberdb',
|
|
60
|
+
speeds: {
|
|
61
|
+
findKeysMax: 30,
|
|
62
|
+
},
|
|
61
63
|
},
|
|
62
|
-
*/
|
|
63
64
|
};
|