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 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(2, "Setting up Amazon S3 (" + aws_config.region + ")");
36
- this.logDebug(3, "S3 Bucket ID: " + s3_config.params.Bucket);
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 = inp;
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 = inp;
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.length) {
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(/^\//) ? Tools.lookupPath(def.source, record) : Tools.sub(def.source, record, true);
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(/^\//) ? Tools.lookupPath(def.source, record) : Tools.sub(def.source, record, true);
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.lookupPath(sorter.source, record);
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.14",
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(2, "Setting up storage system v" + this.version);
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 = {};