pixl-server-storage 3.1.13 → 3.1.15
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/docs/API.md +25 -0
- package/docs/Transactions.md +2 -1
- package/engines/S3.js +63 -4
- package/hash.js +19 -0
- package/indexer.js +11 -11
- package/package.json +2 -1
- package/transaction.js +2 -1
package/docs/API.md
CHANGED
|
@@ -17,6 +17,7 @@ var storage = server.Storage;
|
|
|
17
17
|
* [putStream](#putstream)
|
|
18
18
|
* [get](#get)
|
|
19
19
|
* [getMulti](#getmulti)
|
|
20
|
+
* [getBuffer](#getbuffer)
|
|
20
21
|
* [getStream](#getstream)
|
|
21
22
|
* [getStreamRange](#getstreamrange)
|
|
22
23
|
* [head](#head)
|
|
@@ -60,6 +61,8 @@ var storage = server.Storage;
|
|
|
60
61
|
* [hashPutMulti](#hashputmulti)
|
|
61
62
|
* [hashGet](#hashget)
|
|
62
63
|
* [hashGetMulti](#hashgetmulti)
|
|
64
|
+
* [hashUpdate](#hashupdate)
|
|
65
|
+
* [hashUpdateMulti](#hashupdatemulti)
|
|
63
66
|
* [hashEach](#hasheach)
|
|
64
67
|
* [hashEachSync](#hasheachsync)
|
|
65
68
|
* [hashEachPage](#hasheachpage)
|
|
@@ -1012,6 +1015,28 @@ storage.hashUpdate( 'users', 'bsanders', { age: 81 }, function(err) {
|
|
|
1012
1015
|
|
|
1013
1016
|
This would update Bernie's age to 81 without affecting any of the other properties in his user record.
|
|
1014
1017
|
|
|
1018
|
+
## hashUpdateMulti
|
|
1019
|
+
|
|
1020
|
+
```javascript
|
|
1021
|
+
storage.hashUpdateMulti( PATH, RECORDS, CALLBACK );
|
|
1022
|
+
```
|
|
1023
|
+
|
|
1024
|
+
The `hashUpdateMulti()` method updates multiple key/value pairs in a hash. The `PATH` specifies the main storage path of the hash, and `RECORDS` should be an object containing all the keys and values you want to update. See [hashUpdate()](#hashupdate) for details on the update format.
|
|
1025
|
+
|
|
1026
|
+
```js
|
|
1027
|
+
var updates = {
|
|
1028
|
+
"bsanders": { age: 81 },
|
|
1029
|
+
"hclinton": { age: 75 },
|
|
1030
|
+
"dtrump": { age: 77 }
|
|
1031
|
+
};
|
|
1032
|
+
|
|
1033
|
+
storage.hashUpdateMulti( 'users', records, function(err) {
|
|
1034
|
+
if (err) throw err;
|
|
1035
|
+
} );
|
|
1036
|
+
```
|
|
1037
|
+
|
|
1038
|
+
This would update the `age` properties in each record, without affecting the other data.
|
|
1039
|
+
|
|
1015
1040
|
## hashEach
|
|
1016
1041
|
|
|
1017
1042
|
```javascript
|
package/docs/Transactions.md
CHANGED
|
@@ -199,8 +199,9 @@ When transactions are enabled, many compound storage methods (those that execute
|
|
|
199
199
|
- **Hash Methods**
|
|
200
200
|
- [hashCreate()](API.md#hashcreate)
|
|
201
201
|
- [hashPut()](API.md#hashput)
|
|
202
|
-
- [hashUpdate()](API.md#hashupdate)
|
|
203
202
|
- [hashPutMulti()](API.md#hashputmulti)
|
|
203
|
+
- [hashUpdate()](API.md#hashupdate)
|
|
204
|
+
- [hashUpdateMulti()](API.md#hashupdatemulti)
|
|
204
205
|
- [hashCopy()](API.md#hashcopy)
|
|
205
206
|
- [hashRename()](API.md#hashrename)
|
|
206
207
|
- [hashDeleteMulti()](API.md#hashdeletemulti)
|
package/engines/S3.js
CHANGED
|
@@ -10,6 +10,7 @@ var S3 = require("@aws-sdk/client-s3");
|
|
|
10
10
|
var { Upload } = require("@aws-sdk/lib-storage");
|
|
11
11
|
var { NodeHttpHandler } = require("@smithy/node-http-handler");
|
|
12
12
|
var streamToBuffer = require("fast-stream-to-buffer");
|
|
13
|
+
var StreamMeter = require("stream-meter");
|
|
13
14
|
|
|
14
15
|
module.exports = Class.create({
|
|
15
16
|
|
|
@@ -100,8 +101,6 @@ module.exports = Class.create({
|
|
|
100
101
|
|
|
101
102
|
prepKey: function(key) {
|
|
102
103
|
// prepare key for S3 based on config
|
|
103
|
-
var md5 = Tools.digestHex(key, 'md5');
|
|
104
|
-
|
|
105
104
|
var ns = '';
|
|
106
105
|
if (key.match(/^([\w\-\.]+)\//)) ns = RegExp.$1;
|
|
107
106
|
|
|
@@ -110,6 +109,7 @@ module.exports = Class.create({
|
|
|
110
109
|
}
|
|
111
110
|
|
|
112
111
|
if (this.keyTemplate) {
|
|
112
|
+
var md5 = Tools.digestHex(key, 'md5');
|
|
113
113
|
var idx = 0;
|
|
114
114
|
var temp = this.keyTemplate.replace( /\#/g, function() {
|
|
115
115
|
return md5.substr(idx++, 1);
|
|
@@ -153,6 +153,7 @@ module.exports = Class.create({
|
|
|
153
153
|
this.s3.send( new S3.PutObjectCommand(params) )
|
|
154
154
|
.then( function(data) {
|
|
155
155
|
self.logDebug(9, "Store complete: " + key);
|
|
156
|
+
self.storage.emit('billing', 's3_put', 1);
|
|
156
157
|
|
|
157
158
|
// possibly cache in LRU
|
|
158
159
|
if (self.cache && !is_binary) {
|
|
@@ -162,6 +163,13 @@ module.exports = Class.create({
|
|
|
162
163
|
if (callback) process.nextTick( function() { callback(null, data); });
|
|
163
164
|
} )
|
|
164
165
|
.catch( function(err) {
|
|
166
|
+
if (err.name == 'SlowDown') {
|
|
167
|
+
// special behavior for SlowDown errors
|
|
168
|
+
self.logDebug(6, "Received SlowDown from S3 put: " + orig_key + ": " + err + " (will retry)");
|
|
169
|
+
self.storage.emit('slowDown');
|
|
170
|
+
setTimeout( function() { self.put(orig_key, value, callback); }, 1000 );
|
|
171
|
+
return;
|
|
172
|
+
}
|
|
165
173
|
self.logError('s3', "Failed to store object: " + key + ": " + (err.message || err), err);
|
|
166
174
|
if (callback) process.nextTick( function() { callback(err); });
|
|
167
175
|
} );
|
|
@@ -173,9 +181,12 @@ module.exports = Class.create({
|
|
|
173
181
|
var orig_key = key;
|
|
174
182
|
key = this.prepKey(key);
|
|
175
183
|
|
|
184
|
+
var meter = new StreamMeter();
|
|
185
|
+
inp.pipe(meter);
|
|
186
|
+
|
|
176
187
|
var params = Tools.copyHash( this.s3Params );
|
|
177
188
|
params.Key = this.extKey(key, orig_key);
|
|
178
|
-
params.Body =
|
|
189
|
+
params.Body = meter;
|
|
179
190
|
|
|
180
191
|
this.logDebug(9, "Storing S3 Binary Stream: " + key);
|
|
181
192
|
|
|
@@ -187,6 +198,8 @@ module.exports = Class.create({
|
|
|
187
198
|
upload.done()
|
|
188
199
|
.then( function(data) {
|
|
189
200
|
self.logDebug(9, "Stream store complete: " + key);
|
|
201
|
+
self.storage.emit('billing', 's3_put', 1);
|
|
202
|
+
self.storage.emit('billing', 's3_bytes_out', meter.bytes);
|
|
190
203
|
if (callback) process.nextTick( function() { callback(null, data); });
|
|
191
204
|
} )
|
|
192
205
|
.catch( function(err) {
|
|
@@ -201,9 +214,12 @@ module.exports = Class.create({
|
|
|
201
214
|
var orig_key = key;
|
|
202
215
|
key = this.prepKey(key);
|
|
203
216
|
|
|
217
|
+
var meter = new StreamMeter();
|
|
218
|
+
inp.pipe(meter);
|
|
219
|
+
|
|
204
220
|
var params = Tools.copyHash( this.s3Params );
|
|
205
221
|
params.Key = this.extKey(key, orig_key);
|
|
206
|
-
params.Body =
|
|
222
|
+
params.Body = meter;
|
|
207
223
|
if (opts) Tools.mergeHashInto(params, opts);
|
|
208
224
|
|
|
209
225
|
this.logDebug(9, "Storing S3 Binary Stream: " + key);
|
|
@@ -216,6 +232,8 @@ module.exports = Class.create({
|
|
|
216
232
|
upload.done()
|
|
217
233
|
.then( function(data) {
|
|
218
234
|
self.logDebug(9, "Stream store complete: " + key);
|
|
235
|
+
self.storage.emit('billing', 's3_put', 1);
|
|
236
|
+
self.storage.emit('billing', 's3_bytes_out', meter.bytes);
|
|
219
237
|
if (callback) process.nextTick( function() { callback(null, data); });
|
|
220
238
|
} )
|
|
221
239
|
.catch( function(err) {
|
|
@@ -252,6 +270,7 @@ module.exports = Class.create({
|
|
|
252
270
|
this.s3.send( new S3.HeadObjectCommand(params) )
|
|
253
271
|
.then( function(data) {
|
|
254
272
|
self.logDebug(9, "Head complete: " + key);
|
|
273
|
+
self.storage.emit('billing', 's3_head', 1);
|
|
255
274
|
|
|
256
275
|
process.nextTick( function() {
|
|
257
276
|
callback( null, {
|
|
@@ -267,6 +286,13 @@ module.exports = Class.create({
|
|
|
267
286
|
err = new Error("Failed to head key: " + key + ": Not found");
|
|
268
287
|
err.code = "NoSuchKey";
|
|
269
288
|
}
|
|
289
|
+
else if (err.name == 'SlowDown') {
|
|
290
|
+
// special behavior for SlowDown errors
|
|
291
|
+
self.logDebug(6, "Received SlowDown from S3 head: " + orig_key + ": " + err + " (will retry)");
|
|
292
|
+
self.storage.emit('slowDown');
|
|
293
|
+
setTimeout( function() { self.head(orig_key, callback); }, 1000 );
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
270
296
|
else {
|
|
271
297
|
// some other error
|
|
272
298
|
self.logError('s3', "Failed to head key: " + key + ": " + (err.message || err), err);
|
|
@@ -314,6 +340,9 @@ module.exports = Class.create({
|
|
|
314
340
|
return callback(err);
|
|
315
341
|
}
|
|
316
342
|
|
|
343
|
+
self.storage.emit('billing', 's3_get', 1);
|
|
344
|
+
self.storage.emit('billing', 's3_bytes_in', body.length);
|
|
345
|
+
|
|
317
346
|
if (is_binary) {
|
|
318
347
|
self.logDebug(9, "Binary fetch complete: " + key, '' + body.length + ' bytes');
|
|
319
348
|
}
|
|
@@ -347,6 +376,13 @@ module.exports = Class.create({
|
|
|
347
376
|
err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
348
377
|
err.code = "NoSuchKey";
|
|
349
378
|
}
|
|
379
|
+
else if (err.name == 'SlowDown') {
|
|
380
|
+
// special behavior for SlowDown errors
|
|
381
|
+
self.logDebug(6, "Received SlowDown from S3 get: " + orig_key + ": " + err + " (will retry)");
|
|
382
|
+
self.storage.emit('slowDown');
|
|
383
|
+
setTimeout( function() { self.get(orig_key, callback); }, 1000 );
|
|
384
|
+
return;
|
|
385
|
+
}
|
|
350
386
|
else {
|
|
351
387
|
// some other error
|
|
352
388
|
self.logError('s3', "Failed to fetch key: " + key + ": " + (err.message || err), err);
|
|
@@ -376,6 +412,8 @@ module.exports = Class.create({
|
|
|
376
412
|
}
|
|
377
413
|
|
|
378
414
|
self.logDebug(9, "Binary fetch complete: " + key, '' + body.length + ' bytes');
|
|
415
|
+
self.storage.emit('billing', 's3_get', 1);
|
|
416
|
+
self.storage.emit('billing', 's3_bytes_in', body.length);
|
|
379
417
|
|
|
380
418
|
callback( null, body, {
|
|
381
419
|
mod: Math.floor((new Date(data.LastModified)).getTime() / 1000),
|
|
@@ -390,6 +428,13 @@ module.exports = Class.create({
|
|
|
390
428
|
err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
391
429
|
err.code = "NoSuchKey";
|
|
392
430
|
}
|
|
431
|
+
else if (err.name == 'SlowDown') {
|
|
432
|
+
// special behavior for SlowDown errors
|
|
433
|
+
self.logDebug(6, "Received SlowDown from S3 getBuffer: " + orig_key + ": " + err + " (will retry)");
|
|
434
|
+
self.storage.emit('slowDown');
|
|
435
|
+
setTimeout( function() { self.getBuffer(orig_key, callback); }, 1000 );
|
|
436
|
+
return;
|
|
437
|
+
}
|
|
393
438
|
else {
|
|
394
439
|
// some other error
|
|
395
440
|
self.logError('s3', "Failed to fetch key: " + key + ": " + (err.message || err), err);
|
|
@@ -423,6 +468,9 @@ module.exports = Class.create({
|
|
|
423
468
|
self.logDebug(9, "S3 stream download closed: " + key);
|
|
424
469
|
} );
|
|
425
470
|
|
|
471
|
+
self.storage.emit('billing', 's3_get', 1);
|
|
472
|
+
self.storage.emit('billing', 's3_bytes_in', data.ContentLength);
|
|
473
|
+
|
|
426
474
|
process.nextTick( function() {
|
|
427
475
|
callback( null, download, {
|
|
428
476
|
mod: Math.floor( (new Date(data.LastModified)).getTime() / 1000 ),
|
|
@@ -478,6 +526,9 @@ module.exports = Class.create({
|
|
|
478
526
|
self.logDebug(9, "S3 stream download closed: " + key);
|
|
479
527
|
} );
|
|
480
528
|
|
|
529
|
+
self.storage.emit('billing', 's3_get', 1);
|
|
530
|
+
self.storage.emit('billing', 's3_bytes_in', data.ContentRange);
|
|
531
|
+
|
|
481
532
|
// get full length from the ContentRange header
|
|
482
533
|
var len = 0;
|
|
483
534
|
if (data.ContentRange && data.ContentRange.toString().match(/\/\s*(\d+)\s*$/)) {
|
|
@@ -521,6 +572,7 @@ module.exports = Class.create({
|
|
|
521
572
|
this.s3.send( new S3.DeleteObjectCommand(params) )
|
|
522
573
|
.then( function(data) {
|
|
523
574
|
self.logDebug(9, "Delete complete: " + key);
|
|
575
|
+
self.storage.emit('billing', 's3_delete', 1);
|
|
524
576
|
|
|
525
577
|
// possibly delete from LRU cache as well
|
|
526
578
|
if (self.cache && self.cache.has(orig_key)) {
|
|
@@ -530,6 +582,13 @@ module.exports = Class.create({
|
|
|
530
582
|
if (callback) process.nextTick( function() { callback(null, data); } );
|
|
531
583
|
} )
|
|
532
584
|
.catch( function(err) {
|
|
585
|
+
if (err.name == 'SlowDown') {
|
|
586
|
+
// special behavior for SlowDown errors
|
|
587
|
+
self.logDebug(6, "Received SlowDown from S3 delete: " + orig_key + ": " + err + " (will retry)");
|
|
588
|
+
self.storage.emit('slowDown');
|
|
589
|
+
setTimeout( function() { self.delete(orig_key, callback); }, 1000 );
|
|
590
|
+
return;
|
|
591
|
+
}
|
|
533
592
|
self.logError('s3', "Failed to delete object: " + key + ": " + (err.message || err), err);
|
|
534
593
|
if (callback) process.nextTick( function() { callback(err); } );
|
|
535
594
|
} );
|
package/hash.js
CHANGED
|
@@ -426,6 +426,25 @@ module.exports = Class.create({
|
|
|
426
426
|
}); // get
|
|
427
427
|
},
|
|
428
428
|
|
|
429
|
+
hashUpdateMulti: function(path, records, callback) {
|
|
430
|
+
// update multiple hash records at once, given object of keys and values
|
|
431
|
+
// need concurrency limit of 1 because hashUpdate locks
|
|
432
|
+
var self = this;
|
|
433
|
+
|
|
434
|
+
async.eachLimit(Object.keys(records), 1,
|
|
435
|
+
function(hkey, callback) {
|
|
436
|
+
// iterator for each key
|
|
437
|
+
self.hashUpdate(path, hkey, records[hkey], function(err) {
|
|
438
|
+
callback(err);
|
|
439
|
+
} );
|
|
440
|
+
},
|
|
441
|
+
function(err) {
|
|
442
|
+
// all keys updated
|
|
443
|
+
callback(err);
|
|
444
|
+
}
|
|
445
|
+
);
|
|
446
|
+
},
|
|
447
|
+
|
|
429
448
|
hashEachPage: function(path, iterator, callback) {
|
|
430
449
|
// call user iterator for each populated hash page, data only
|
|
431
450
|
// iterator will be passed page items hash object
|
package/indexer.js
CHANGED
|
@@ -75,7 +75,7 @@ module.exports = Class.create({
|
|
|
75
75
|
validateIndexConfig: function(config) {
|
|
76
76
|
// make sure index config is kosher
|
|
77
77
|
// return false for success, or error on failure
|
|
78
|
-
if (!config || !config.fields || !config.fields
|
|
78
|
+
if (!config || !config.fields || !Tools.isaArray(config.fields)) {
|
|
79
79
|
return( new Error("Invalid index configuration object.") );
|
|
80
80
|
}
|
|
81
81
|
if (Tools.findObject(config.fields, { _primary: 1 })) {
|
|
@@ -104,6 +104,10 @@ module.exports = Class.create({
|
|
|
104
104
|
|
|
105
105
|
// validate each sorter def
|
|
106
106
|
if (config.sorters) {
|
|
107
|
+
if (!Tools.isaArray(config.sorters)) {
|
|
108
|
+
return( new Error("Invalid index sorters array.") );
|
|
109
|
+
}
|
|
110
|
+
|
|
107
111
|
for (var idx = 0, len = config.sorters.length; idx < len; idx++) {
|
|
108
112
|
var sorter = config.sorters[idx];
|
|
109
113
|
|
|
@@ -163,18 +167,12 @@ module.exports = Class.create({
|
|
|
163
167
|
var fields = [];
|
|
164
168
|
|
|
165
169
|
config.fields.forEach( function(def) {
|
|
166
|
-
var value = def.source.match(
|
|
170
|
+
var value = def.source.match(/\[.+\]/) ? Tools.sub(def.source, record, true) : Tools.getPath(record, def.source);
|
|
171
|
+
if (value === undefined) value = null;
|
|
167
172
|
if ((value === null) && ("default_value" in def)) value = def.default_value;
|
|
168
173
|
if (value !== null) fields.push(def);
|
|
169
174
|
} );
|
|
170
175
|
|
|
171
|
-
if (!fields.length) {
|
|
172
|
-
// nothing to index!
|
|
173
|
-
this.logDebug(6, "Nothing to index, skipping entire record");
|
|
174
|
-
if (callback) callback();
|
|
175
|
-
return;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
176
|
// start index and track perf
|
|
179
177
|
var pf = this.perf.begin('index');
|
|
180
178
|
|
|
@@ -211,7 +209,8 @@ module.exports = Class.create({
|
|
|
211
209
|
return;
|
|
212
210
|
}
|
|
213
211
|
|
|
214
|
-
var value = def.source.match(
|
|
212
|
+
var value = def.source.match(/\[.+\]/) ? Tools.sub(def.source, record, true) : Tools.getPath(record, def.source);
|
|
213
|
+
if (value === undefined) value = null;
|
|
215
214
|
if ((value === null) && ("default_value" in def)) value = def.default_value;
|
|
216
215
|
if (typeof(value) == 'object') value = JSON.stringify(value);
|
|
217
216
|
|
|
@@ -920,7 +919,8 @@ module.exports = Class.create({
|
|
|
920
919
|
// add record to sorter index
|
|
921
920
|
var config = state.config;
|
|
922
921
|
|
|
923
|
-
var value = Tools.
|
|
922
|
+
var value = Tools.getPath(record, sorter.source);
|
|
923
|
+
if (value === undefined) value = null;
|
|
924
924
|
if ((value === null) && ("default_value" in sorter)) value = sorter.default_value;
|
|
925
925
|
if (value === null) {
|
|
926
926
|
if (state.new_record) value = ((sorter.type == 'number') ? 0 : '');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixl-server-storage",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.15",
|
|
4
4
|
"description": "A key/value/list storage component for the pixl-server framework.",
|
|
5
5
|
"author": "Joseph Huckaby <jhuckaby@gmail.com>",
|
|
6
6
|
"homepage": "https://github.com/jhuckaby/pixl-server-storage",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
"pixl-server": "^1.0.39",
|
|
37
37
|
"pixl-tools": "^1.0.33",
|
|
38
38
|
"porter-stemmer": "0.9.1",
|
|
39
|
+
"stream-meter": "1.0.4",
|
|
39
40
|
"unidecode": "0.1.8"
|
|
40
41
|
},
|
|
41
42
|
"devDependencies": {
|
package/transaction.js
CHANGED