pixl-server-storage 3.1.14 → 3.1.16
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/engines/S3.js +68 -7
- package/indexer.js +16 -11
- package/package.json +2 -1
- package/storage.js +1 -1
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
|
|
|
@@ -32,14 +33,15 @@ module.exports = Class.create({
|
|
|
32
33
|
var aws_config = this.storage.config.get('AWS') || this.server.config.get('AWS');
|
|
33
34
|
var s3_config = this.config.get();
|
|
34
35
|
|
|
35
|
-
this.logDebug(
|
|
36
|
-
this.logDebug(
|
|
36
|
+
this.logDebug(5, "Setting up Amazon S3 (" + aws_config.region + ")");
|
|
37
|
+
this.logDebug(6, "S3 Bucket ID: " + s3_config.params.Bucket);
|
|
37
38
|
|
|
38
39
|
this.keyPrefix = (s3_config.keyPrefix || '').replace(/^\//, '');
|
|
39
40
|
if (this.keyPrefix && !this.keyPrefix.match(/\/$/)) this.keyPrefix += '/';
|
|
40
41
|
|
|
41
42
|
this.keyTemplate = (s3_config.keyTemplate || '').replace(/^\//, '').replace(/\/$/, '');
|
|
42
43
|
this.fileExtensions = !!s3_config.fileExtensions;
|
|
44
|
+
this.pretty = !!s3_config.pretty;
|
|
43
45
|
|
|
44
46
|
// optional LRU cache
|
|
45
47
|
this.cache = null;
|
|
@@ -80,6 +82,7 @@ module.exports = Class.create({
|
|
|
80
82
|
delete combo_config.keyPrefix;
|
|
81
83
|
delete combo_config.keyTemplate;
|
|
82
84
|
delete combo_config.fileExtensions;
|
|
85
|
+
delete combo_config.pretty;
|
|
83
86
|
delete combo_config.cache;
|
|
84
87
|
|
|
85
88
|
this.s3Params = combo_config.params || {};
|
|
@@ -100,8 +103,6 @@ module.exports = Class.create({
|
|
|
100
103
|
|
|
101
104
|
prepKey: function(key) {
|
|
102
105
|
// prepare key for S3 based on config
|
|
103
|
-
var md5 = Tools.digestHex(key, 'md5');
|
|
104
|
-
|
|
105
106
|
var ns = '';
|
|
106
107
|
if (key.match(/^([\w\-\.]+)\//)) ns = RegExp.$1;
|
|
107
108
|
|
|
@@ -110,6 +111,7 @@ module.exports = Class.create({
|
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
if (this.keyTemplate) {
|
|
114
|
+
var md5 = Tools.digestHex(key, 'md5');
|
|
113
115
|
var idx = 0;
|
|
114
116
|
var temp = this.keyTemplate.replace( /\#/g, function() {
|
|
115
117
|
return md5.substr(idx++, 1);
|
|
@@ -146,13 +148,14 @@ module.exports = Class.create({
|
|
|
146
148
|
}
|
|
147
149
|
else {
|
|
148
150
|
this.logDebug(9, "Storing S3 JSON Object: " + key, this.debugLevel(10) ? params.Body : null);
|
|
149
|
-
params.Body = JSON.stringify( params.Body );
|
|
151
|
+
params.Body = this.pretty ? JSON.stringify( params.Body, null, "\t" ) : JSON.stringify( params.Body );
|
|
150
152
|
params.ContentType = 'application/json';
|
|
151
153
|
}
|
|
152
154
|
|
|
153
155
|
this.s3.send( new S3.PutObjectCommand(params) )
|
|
154
156
|
.then( function(data) {
|
|
155
157
|
self.logDebug(9, "Store complete: " + key);
|
|
158
|
+
self.storage.emit('billing', 's3_put', 1);
|
|
156
159
|
|
|
157
160
|
// possibly cache in LRU
|
|
158
161
|
if (self.cache && !is_binary) {
|
|
@@ -162,6 +165,13 @@ module.exports = Class.create({
|
|
|
162
165
|
if (callback) process.nextTick( function() { callback(null, data); });
|
|
163
166
|
} )
|
|
164
167
|
.catch( function(err) {
|
|
168
|
+
if (err.name == 'SlowDown') {
|
|
169
|
+
// special behavior for SlowDown errors
|
|
170
|
+
self.logDebug(6, "Received SlowDown from S3 put: " + orig_key + ": " + err + " (will retry)");
|
|
171
|
+
self.storage.emit('slowDown');
|
|
172
|
+
setTimeout( function() { self.put(orig_key, value, callback); }, 1000 );
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
165
175
|
self.logError('s3', "Failed to store object: " + key + ": " + (err.message || err), err);
|
|
166
176
|
if (callback) process.nextTick( function() { callback(err); });
|
|
167
177
|
} );
|
|
@@ -173,9 +183,12 @@ module.exports = Class.create({
|
|
|
173
183
|
var orig_key = key;
|
|
174
184
|
key = this.prepKey(key);
|
|
175
185
|
|
|
186
|
+
var meter = new StreamMeter();
|
|
187
|
+
inp.pipe(meter);
|
|
188
|
+
|
|
176
189
|
var params = Tools.copyHash( this.s3Params );
|
|
177
190
|
params.Key = this.extKey(key, orig_key);
|
|
178
|
-
params.Body =
|
|
191
|
+
params.Body = meter;
|
|
179
192
|
|
|
180
193
|
this.logDebug(9, "Storing S3 Binary Stream: " + key);
|
|
181
194
|
|
|
@@ -187,6 +200,8 @@ module.exports = Class.create({
|
|
|
187
200
|
upload.done()
|
|
188
201
|
.then( function(data) {
|
|
189
202
|
self.logDebug(9, "Stream store complete: " + key);
|
|
203
|
+
self.storage.emit('billing', 's3_put', 1);
|
|
204
|
+
self.storage.emit('billing', 's3_bytes_out', meter.bytes);
|
|
190
205
|
if (callback) process.nextTick( function() { callback(null, data); });
|
|
191
206
|
} )
|
|
192
207
|
.catch( function(err) {
|
|
@@ -201,9 +216,12 @@ module.exports = Class.create({
|
|
|
201
216
|
var orig_key = key;
|
|
202
217
|
key = this.prepKey(key);
|
|
203
218
|
|
|
219
|
+
var meter = new StreamMeter();
|
|
220
|
+
inp.pipe(meter);
|
|
221
|
+
|
|
204
222
|
var params = Tools.copyHash( this.s3Params );
|
|
205
223
|
params.Key = this.extKey(key, orig_key);
|
|
206
|
-
params.Body =
|
|
224
|
+
params.Body = meter;
|
|
207
225
|
if (opts) Tools.mergeHashInto(params, opts);
|
|
208
226
|
|
|
209
227
|
this.logDebug(9, "Storing S3 Binary Stream: " + key);
|
|
@@ -216,6 +234,8 @@ module.exports = Class.create({
|
|
|
216
234
|
upload.done()
|
|
217
235
|
.then( function(data) {
|
|
218
236
|
self.logDebug(9, "Stream store complete: " + key);
|
|
237
|
+
self.storage.emit('billing', 's3_put', 1);
|
|
238
|
+
self.storage.emit('billing', 's3_bytes_out', meter.bytes);
|
|
219
239
|
if (callback) process.nextTick( function() { callback(null, data); });
|
|
220
240
|
} )
|
|
221
241
|
.catch( function(err) {
|
|
@@ -252,6 +272,7 @@ module.exports = Class.create({
|
|
|
252
272
|
this.s3.send( new S3.HeadObjectCommand(params) )
|
|
253
273
|
.then( function(data) {
|
|
254
274
|
self.logDebug(9, "Head complete: " + key);
|
|
275
|
+
self.storage.emit('billing', 's3_head', 1);
|
|
255
276
|
|
|
256
277
|
process.nextTick( function() {
|
|
257
278
|
callback( null, {
|
|
@@ -267,6 +288,13 @@ module.exports = Class.create({
|
|
|
267
288
|
err = new Error("Failed to head key: " + key + ": Not found");
|
|
268
289
|
err.code = "NoSuchKey";
|
|
269
290
|
}
|
|
291
|
+
else if (err.name == 'SlowDown') {
|
|
292
|
+
// special behavior for SlowDown errors
|
|
293
|
+
self.logDebug(6, "Received SlowDown from S3 head: " + orig_key + ": " + err + " (will retry)");
|
|
294
|
+
self.storage.emit('slowDown');
|
|
295
|
+
setTimeout( function() { self.head(orig_key, callback); }, 1000 );
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
270
298
|
else {
|
|
271
299
|
// some other error
|
|
272
300
|
self.logError('s3', "Failed to head key: " + key + ": " + (err.message || err), err);
|
|
@@ -314,6 +342,9 @@ module.exports = Class.create({
|
|
|
314
342
|
return callback(err);
|
|
315
343
|
}
|
|
316
344
|
|
|
345
|
+
self.storage.emit('billing', 's3_get', 1);
|
|
346
|
+
self.storage.emit('billing', 's3_bytes_in', body.length);
|
|
347
|
+
|
|
317
348
|
if (is_binary) {
|
|
318
349
|
self.logDebug(9, "Binary fetch complete: " + key, '' + body.length + ' bytes');
|
|
319
350
|
}
|
|
@@ -347,6 +378,13 @@ module.exports = Class.create({
|
|
|
347
378
|
err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
348
379
|
err.code = "NoSuchKey";
|
|
349
380
|
}
|
|
381
|
+
else if (err.name == 'SlowDown') {
|
|
382
|
+
// special behavior for SlowDown errors
|
|
383
|
+
self.logDebug(6, "Received SlowDown from S3 get: " + orig_key + ": " + err + " (will retry)");
|
|
384
|
+
self.storage.emit('slowDown');
|
|
385
|
+
setTimeout( function() { self.get(orig_key, callback); }, 1000 );
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
350
388
|
else {
|
|
351
389
|
// some other error
|
|
352
390
|
self.logError('s3', "Failed to fetch key: " + key + ": " + (err.message || err), err);
|
|
@@ -376,6 +414,8 @@ module.exports = Class.create({
|
|
|
376
414
|
}
|
|
377
415
|
|
|
378
416
|
self.logDebug(9, "Binary fetch complete: " + key, '' + body.length + ' bytes');
|
|
417
|
+
self.storage.emit('billing', 's3_get', 1);
|
|
418
|
+
self.storage.emit('billing', 's3_bytes_in', body.length);
|
|
379
419
|
|
|
380
420
|
callback( null, body, {
|
|
381
421
|
mod: Math.floor((new Date(data.LastModified)).getTime() / 1000),
|
|
@@ -390,6 +430,13 @@ module.exports = Class.create({
|
|
|
390
430
|
err = new Error("Failed to fetch key: " + key + ": Not found");
|
|
391
431
|
err.code = "NoSuchKey";
|
|
392
432
|
}
|
|
433
|
+
else if (err.name == 'SlowDown') {
|
|
434
|
+
// special behavior for SlowDown errors
|
|
435
|
+
self.logDebug(6, "Received SlowDown from S3 getBuffer: " + orig_key + ": " + err + " (will retry)");
|
|
436
|
+
self.storage.emit('slowDown');
|
|
437
|
+
setTimeout( function() { self.getBuffer(orig_key, callback); }, 1000 );
|
|
438
|
+
return;
|
|
439
|
+
}
|
|
393
440
|
else {
|
|
394
441
|
// some other error
|
|
395
442
|
self.logError('s3', "Failed to fetch key: " + key + ": " + (err.message || err), err);
|
|
@@ -423,6 +470,9 @@ module.exports = Class.create({
|
|
|
423
470
|
self.logDebug(9, "S3 stream download closed: " + key);
|
|
424
471
|
} );
|
|
425
472
|
|
|
473
|
+
self.storage.emit('billing', 's3_get', 1);
|
|
474
|
+
self.storage.emit('billing', 's3_bytes_in', data.ContentLength);
|
|
475
|
+
|
|
426
476
|
process.nextTick( function() {
|
|
427
477
|
callback( null, download, {
|
|
428
478
|
mod: Math.floor( (new Date(data.LastModified)).getTime() / 1000 ),
|
|
@@ -478,6 +528,9 @@ module.exports = Class.create({
|
|
|
478
528
|
self.logDebug(9, "S3 stream download closed: " + key);
|
|
479
529
|
} );
|
|
480
530
|
|
|
531
|
+
self.storage.emit('billing', 's3_get', 1);
|
|
532
|
+
self.storage.emit('billing', 's3_bytes_in', data.ContentRange);
|
|
533
|
+
|
|
481
534
|
// get full length from the ContentRange header
|
|
482
535
|
var len = 0;
|
|
483
536
|
if (data.ContentRange && data.ContentRange.toString().match(/\/\s*(\d+)\s*$/)) {
|
|
@@ -521,6 +574,7 @@ module.exports = Class.create({
|
|
|
521
574
|
this.s3.send( new S3.DeleteObjectCommand(params) )
|
|
522
575
|
.then( function(data) {
|
|
523
576
|
self.logDebug(9, "Delete complete: " + key);
|
|
577
|
+
self.storage.emit('billing', 's3_delete', 1);
|
|
524
578
|
|
|
525
579
|
// possibly delete from LRU cache as well
|
|
526
580
|
if (self.cache && self.cache.has(orig_key)) {
|
|
@@ -530,6 +584,13 @@ module.exports = Class.create({
|
|
|
530
584
|
if (callback) process.nextTick( function() { callback(null, data); } );
|
|
531
585
|
} )
|
|
532
586
|
.catch( function(err) {
|
|
587
|
+
if (err.name == 'SlowDown') {
|
|
588
|
+
// special behavior for SlowDown errors
|
|
589
|
+
self.logDebug(6, "Received SlowDown from S3 delete: " + orig_key + ": " + err + " (will retry)");
|
|
590
|
+
self.storage.emit('slowDown');
|
|
591
|
+
setTimeout( function() { self.delete(orig_key, callback); }, 1000 );
|
|
592
|
+
return;
|
|
593
|
+
}
|
|
533
594
|
self.logError('s3', "Failed to delete object: " + key + ": " + (err.message || err), err);
|
|
534
595
|
if (callback) process.nextTick( function() { callback(err); } );
|
|
535
596
|
} );
|
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 : '');
|
|
@@ -975,6 +975,11 @@ module.exports = Class.create({
|
|
|
975
975
|
return value.replace(/\W+/g, '_').replace(/_+/g, '_');
|
|
976
976
|
},
|
|
977
977
|
|
|
978
|
+
filterWords_alphanum_array: function(value) {
|
|
979
|
+
// filter out everything except alphanum + underscore + comma, suitable for JSON arrays
|
|
980
|
+
return value.replace(/[\[\]\"\']+/g, '').replace(/[^\w\,]+/g, '_').replace(/_+/g, '_');
|
|
981
|
+
},
|
|
982
|
+
|
|
978
983
|
getWordList: function(value, def, config) {
|
|
979
984
|
// clean and filter text down to list of alphanumeric words
|
|
980
985
|
// return array of clean words
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixl-server-storage",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.16",
|
|
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/storage.js
CHANGED
|
@@ -56,7 +56,7 @@ module.exports = Class.create({
|
|
|
56
56
|
startup: function(callback) {
|
|
57
57
|
// setup storage plugin
|
|
58
58
|
var self = this;
|
|
59
|
-
this.logDebug(
|
|
59
|
+
this.logDebug(5, "Setting up storage system v" + this.version);
|
|
60
60
|
|
|
61
61
|
// advisory locking system (in RAM, single process only)
|
|
62
62
|
this.locks = {};
|