pixl-server-storage 3.0.24 → 3.1.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/README.md CHANGED
@@ -252,6 +252,12 @@ The `expiration_updates` property activates additional features in the [expirati
252
252
 
253
253
  Note that this feature incurs additional overhead, because the expiration date of every record needs to be stored in a global [Hash](docs/Hashes.md). This slows down both the expiration set operation, and the nightly maintenance sweep to delete expired records. For this reason, the `expiration_dates` property defaults to `false` (disabled).
254
254
 
255
+ ## lower_case_keys
256
+
257
+ The `lower_case_keys` property causes all storage keys to be internally lower-cased, effectively making all storage paths case-insensitive. This is the default (`true`). If you set this property to `false`, then all storage keys retain their natural casing, effectively making them case-sensitive.
258
+
259
+ Please note that if you use the [Local Filesystem](#local-filesystem) engine, then the filesystem itself may be case-insensitive (e.g. legacy macOS HFS).
260
+
255
261
  ## debug (standalone)
256
262
 
257
263
  The `debug` property is only used when using [Standalone Mode](#standalone-mode). Setting this to `true` will cause the engine to emit debugging messages to the console.
@@ -409,27 +415,18 @@ Note that binary records are **not** cached. This system is for JSON records on
409
415
 
410
416
  ## Amazon S3
411
417
 
412
- If you want to use [Amazon S3](http://aws.amazon.com/s3/) as a backing store, here is how to do so. First, you need to manually install the [aws-sdk](https://www.npmjs.com/package/aws-sdk) module into your app:
413
-
414
- ```
415
- npm install --save aws-sdk
416
- ```
417
-
418
- Then configure your storage thusly:
418
+ If you want to use [Amazon S3](http://aws.amazon.com/s3/) as a backing store, configure your storage thusly:
419
419
 
420
420
  ```javascript
421
421
  {
422
422
  "engine": "S3",
423
423
  "AWS": {
424
- "accessKeyId": "YOUR_AMAZON_ACCESS_KEY",
425
- "secretAccessKey": "YOUR_AMAZON_SECRET_KEY",
426
424
  "region": "us-west-1",
427
- "correctClockSkew": true,
428
- "maxRetries": 5,
429
- "httpOptions": {
430
- "connectTimeout": 5000,
431
- "timeout": 5000
432
- }
425
+ "credentials": {
426
+ "accessKeyId": "YOUR_AMAZON_ACCESS_KEY",
427
+ "secretAccessKey": "YOUR_AMAZON_SECRET_KEY",
428
+ },
429
+ "maxAttempts": 5
433
430
  },
434
431
  "S3": {
435
432
  "keyPrefix": "",
@@ -448,10 +445,6 @@ Then configure your storage thusly:
448
445
 
449
446
  Replace `YOUR_AMAZON_ACCESS_KEY` and `YOUR_AMAZON_SECRET_KEY` with your Amazon Access Key and Secret Key, respectively. These can be generated on the Security Credentials page. Replace `MY_S3_BUCKET_ID` with the ID if your own S3 bucket. Make sure you match up the region too.
450
447
 
451
- The `AWS` object is passed directly to the `config.update()` function from the [aws-sdk](https://www.npmjs.com/package/aws-sdk) module, so you can also include any properties supported there. See the [docs](http://docs.aws.amazon.com/AWSJavaScriptSDK/guide/node-configuring.html) for more details.
452
-
453
- The `S3` object is passed directly to the S3 class constructor, so check the [docs](http://docs.aws.amazon.com/AWSJavaScriptSDK/latest/AWS/S3.html#constructor-property) to see what other properties are supported. There are a few special `S3` properties used by pixl-server-storage, which are described below.
454
-
455
448
  If you plan on using Amazon AWS in other parts of your application, you can actually move the `AWS` config object into your outer server configuration. The storage module will look for it there.
456
449
 
457
450
  ### S3 File Extensions
@@ -495,8 +488,6 @@ Besides hash marks, the special macro `[key]` will be substituted with the full
495
488
 
496
489
  It is *highly* recommended that you enable caching for S3, which keeps a copy of the most recently used JSON records in RAM. Not only will this increase overall performance, but it is especially important if you use any of the advanced storage features like [Lists](https://github.com/jhuckaby/pixl-server-storage/blob/master/docs/Lists.md), [Hashes](https://github.com/jhuckaby/pixl-server-storage/blob/master/docs/Hashes.md), [Transactions](https://github.com/jhuckaby/pixl-server-storage/blob/master/docs/Transactions.md) or the [Indexer](https://github.com/jhuckaby/pixl-server-storage/blob/master/docs/Indexer.md).
497
490
 
498
- The reason why caching is so important is because Amazon S3 is only [eventually consistent](https://docs.aws.amazon.com/AmazonS3/latest/dev/Introduction.html#ConsistencyModel) when replacing existing records, so corruption can occur when writing compound objects (i.e. a list or a hash) and then quickly reading them back in. To protect against this, all writes can be cached in RAM, so subsequent reads can often bypass S3 and just read from the RAM cache instead, ensuring that the correct (latest) data is read.
499
-
500
491
  To enable the S3 cache, include a `cache` object in your `S3` configuration with the following properties:
501
492
 
502
493
  ```js
package/engines/S3.js CHANGED
@@ -1,15 +1,13 @@
1
1
  // Amazon AWS S3 Storage Plugin
2
- // Copyright (c) 2015 - 2019 Joseph Huckaby
2
+ // Copyright (c) 2015 - 2022 Joseph Huckaby
3
3
  // Released under the MIT License
4
4
 
5
- // Requires the 'aws-sdk' module from npm
6
- // npm install aws-sdk
7
-
8
5
  var Class = require("pixl-class");
9
6
  var Component = require("pixl-server/component");
10
7
  var Tools = require("pixl-tools");
11
- var AWS = require('aws-sdk');
12
8
  var Cache = require("pixl-cache");
9
+ var S3 = require("@aws-sdk/client-s3");
10
+ var streamToBuffer = require("fast-stream-to-buffer");
13
11
 
14
12
  module.exports = Class.create({
15
13
 
@@ -41,13 +39,6 @@ module.exports = Class.create({
41
39
  this.keyTemplate = (s3_config.keyTemplate || '').replace(/^\//, '').replace(/\/$/, '');
42
40
  this.fileExtensions = !!s3_config.fileExtensions;
43
41
 
44
- if (this.debugLevel(10)) {
45
- // S3 has a logger API but it's extremely verbose -- restrict to level 10 only
46
- s3_config.logger = {
47
- log: function(msg) { self.logDebug(10, "S3 Debug: " + msg); }
48
- };
49
- }
50
-
51
42
  // optional LRU cache
52
43
  this.cache = null;
53
44
  var cache_opts = s3_config.cache;
@@ -63,10 +54,36 @@ module.exports = Class.create({
63
54
  });
64
55
  });
65
56
  }
66
- delete s3_config.cache;
67
57
 
68
- AWS.config.update( aws_config );
69
- this.s3 = new AWS.S3( Tools.copyHashRemoveKeys(s3_config, { keyPrefix:1, keyTemplate:1, fileExtensions:1, cache:1 }) );
58
+ // merge AWS and S3 configs
59
+ var combo_config = Tools.mergeHashes( aws_config, s3_config );
60
+
61
+ // convert v2 config to v3
62
+ if (!combo_config.maxAttempts && combo_config.maxRetries) {
63
+ combo_config.maxAttempts = combo_config.maxRetries;
64
+ delete combo_config.maxRetries;
65
+ }
66
+ if (combo_config.accessKeyId) {
67
+ if (!combo_config.credentials) combo_config.credentials = {};
68
+ combo_config.credentials.accessKeyId = combo_config.accessKeyId;
69
+ delete combo_config.accessKeyId;
70
+ }
71
+ if (combo_config.secretAccessKey) {
72
+ if (!combo_config.credentials) combo_config.credentials = {};
73
+ combo_config.credentials.secretAccessKey = combo_config.secretAccessKey;
74
+ delete combo_config.secretAccessKey;
75
+ }
76
+ delete combo_config.correctClockSkew;
77
+ delete combo_config.httpOptions;
78
+ delete combo_config.keyPrefix;
79
+ delete combo_config.keyTemplate;
80
+ delete combo_config.fileExtensions;
81
+ delete combo_config.cache;
82
+
83
+ this.s3Params = combo_config.params || {};
84
+ delete combo_config.params;
85
+
86
+ this.s3 = new S3.S3Client(combo_config);
70
87
  },
71
88
 
72
89
  prepKey: function(key) {
@@ -107,7 +124,7 @@ module.exports = Class.create({
107
124
  var is_binary = this.storage.isBinaryKey(key);
108
125
  key = this.prepKey(key);
109
126
 
110
- var params = {};
127
+ var params = Tools.copyHash( this.s3Params );
111
128
  params.Key = this.extKey(key, orig_key);
112
129
  params.Body = value;
113
130
 
@@ -121,23 +138,21 @@ module.exports = Class.create({
121
138
  params.ContentType = 'application/json';
122
139
  }
123
140
 
124
- // double-callback protection (bug in aws-sdk)
125
- var done = false;
126
- this.s3.putObject( params, function(err, data) {
127
- if (done) return; else done = true;
128
-
129
- if (err) {
141
+ this.s3.send( new S3.PutObjectCommand(params) )
142
+ .then( function(data) {
143
+ self.logDebug(9, "Store complete: " + key);
144
+
145
+ // possibly cache in LRU
146
+ if (self.cache && !is_binary) {
147
+ self.cache.set( orig_key, params.Body, { date: Tools.timeNow(true) } );
148
+ }
149
+
150
+ if (callback) callback(null, data);
151
+ } )
152
+ .catch( function(err) {
130
153
  self.logError('s3', "Failed to store object: " + key + ": " + (err.message || err), err);
131
- }
132
- else self.logDebug(9, "Store complete: " + key);
133
-
134
- // possibly cache in LRU
135
- if (self.cache && !is_binary) {
136
- self.cache.set( orig_key, params.Body, { date: Tools.timeNow(true) } );
137
- }
138
-
139
- if (callback) callback(err, data);
140
- } );
154
+ if (callback) callback(err);
155
+ } );
141
156
  },
142
157
 
143
158
  putStream: function(key, inp, callback) {
@@ -146,24 +161,21 @@ module.exports = Class.create({
146
161
  var orig_key = key;
147
162
  key = this.prepKey(key);
148
163
 
149
- var params = {};
164
+ var params = Tools.copyHash( this.s3Params );
150
165
  params.Key = this.extKey(key, orig_key);
151
166
  params.Body = inp;
152
167
 
153
168
  this.logDebug(9, "Storing S3 Binary Stream: " + key);
154
169
 
155
- // double-callback protection (bug in aws-sdk)
156
- var done = false;
157
- this.s3.upload(params, function(err, data) {
158
- if (done) return; else done = true;
159
-
160
- if (err) {
170
+ this.s3.send( new S3.PutObjectCommand(params) )
171
+ .then( function(data) {
172
+ self.logDebug(9, "Stream store complete: " + key);
173
+ if (callback) callback(null, data);
174
+ } )
175
+ .catch( function(err) {
161
176
  self.logError('s3', "Failed to store stream: " + key + ": " + (err.message || err), err);
162
- }
163
- else self.logDebug(9, "Stream store complete: " + key);
164
-
165
- if (callback) callback(err, data);
166
- } );
177
+ if (callback) callback(err);
178
+ } );
167
179
  },
168
180
 
169
181
  putStreamCustom: function(key, inp, opts, callback) {
@@ -172,25 +184,22 @@ module.exports = Class.create({
172
184
  var orig_key = key;
173
185
  key = this.prepKey(key);
174
186
 
175
- var params = {};
187
+ var params = Tools.copyHash( this.s3Params );
176
188
  params.Key = this.extKey(key, orig_key);
177
189
  params.Body = inp;
178
190
  if (opts) Tools.mergeHashInto(params, opts);
179
191
 
180
192
  this.logDebug(9, "Storing S3 Binary Stream: " + key);
181
193
 
182
- // double-callback protection (bug in aws-sdk)
183
- var done = false;
184
- this.s3.upload(params, function(err, data) {
185
- if (done) return; else done = true;
186
-
187
- if (err) {
194
+ this.s3.send( new S3.PutObjectCommand(params) )
195
+ .then( function(data) {
196
+ self.logDebug(9, "Stream store complete: " + key);
197
+ if (callback) callback(null, data);
198
+ } )
199
+ .catch( function(err) {
188
200
  self.logError('s3', "Failed to store stream: " + key + ": " + (err.message || err), err);
189
- }
190
- else self.logDebug(9, "Stream store complete: " + key);
191
-
192
- if (callback) callback(err, data);
193
- } );
201
+ if (callback) callback(err);
202
+ } );
194
203
  },
195
204
 
196
205
  head: function(key, callback) {
@@ -214,13 +223,20 @@ module.exports = Class.create({
214
223
  return;
215
224
  } // cache
216
225
 
217
- // double-callback protection (bug in aws-sdk)
218
- var done = false;
219
- this.s3.headObject( { Key: this.extKey(key, orig_key) }, function(err, data) {
220
- if (done) return; else done = true;
221
-
222
- if (err) {
223
- if ((err.code == 'NoSuchKey') || (err.code == 'NotFound')) {
226
+ var params = Tools.copyHash( this.s3Params );
227
+ params.Key = this.extKey(key, orig_key);
228
+
229
+ this.s3.send( new S3.HeadObjectCommand(params) )
230
+ .then( function(data) {
231
+ self.logDebug(9, "Head complete: " + key);
232
+
233
+ callback( null, {
234
+ mod: Math.floor( (new Date(data.LastModified)).getTime() / 1000 ),
235
+ len: data.ContentLength
236
+ } );
237
+ } )
238
+ .catch( function(err) {
239
+ if ((err.name == 'NoSuchKey') || (err.name == 'NotFound') || (err.code == 'NoSuchKey') || (err.code == 'NotFound')) {
224
240
  // key not found, special case, don't log an error
225
241
  // always include "Not found" in error message
226
242
  err = new Error("Failed to head key: " + key + ": Not found");
@@ -230,16 +246,8 @@ module.exports = Class.create({
230
246
  // some other error
231
247
  self.logError('s3', "Failed to head key: " + key + ": " + (err.message || err), err);
232
248
  }
233
- callback( err, null );
234
- return;
235
- }
236
-
237
- self.logDebug(9, "Head complete: " + key);
238
- callback( null, {
239
- mod: Math.floor((new Date(data.LastModified)).getTime() / 1000),
240
- len: data.ContentLength
249
+ callback( err );
241
250
  } );
242
- } );
243
251
  },
244
252
 
245
253
  get: function(key, callback) {
@@ -269,13 +277,46 @@ module.exports = Class.create({
269
277
  return;
270
278
  } // cache
271
279
 
272
- // double-callback protection (bug in aws-sdk)
273
- var done = false;
274
- this.s3.getObject( { Key: this.extKey(key, orig_key) }, function(err, data) {
275
- if (done) return; else done = true;
276
-
277
- if (err) {
278
- if ((err.code == 'NoSuchKey') || (err.code == 'NotFound')) {
280
+ var params = Tools.copyHash( this.s3Params );
281
+ params.Key = this.extKey(key, orig_key);
282
+
283
+ this.s3.send( new S3.GetObjectCommand(params) )
284
+ .then( function(data) {
285
+ // stream to buffer
286
+ streamToBuffer( data.Body, function (err, body) {
287
+ if (err) {
288
+ self.logError('s3', "Failed to fetch key: " + key + ": " + (err.message || err), err);
289
+ return callback(err);
290
+ }
291
+
292
+ if (is_binary) {
293
+ self.logDebug(9, "Binary fetch complete: " + key, '' + body.length + ' bytes');
294
+ }
295
+ else {
296
+ body = body.toString();
297
+
298
+ // possibly cache in LRU
299
+ if (self.cache) {
300
+ self.cache.set( orig_key, body, { date: Tools.timeNow(true) } );
301
+ }
302
+
303
+ try { body = JSON.parse( body ); }
304
+ catch (e) {
305
+ self.logError('s3', "Failed to parse JSON record: " + key + ": " + e);
306
+ callback( e, null );
307
+ return;
308
+ }
309
+ self.logDebug(9, "JSON fetch complete: " + key, self.debugLevel(10) ? body : null);
310
+ }
311
+
312
+ callback( null, body, {
313
+ mod: Math.floor((new Date(data.LastModified)).getTime() / 1000),
314
+ len: data.ContentLength
315
+ } );
316
+ } ); // streamToBuffer
317
+ } )
318
+ .catch( function(err) {
319
+ if ((err.name == 'NoSuchKey') || (err.name == 'NotFound') || (err.code == 'NoSuchKey') || (err.code == 'NotFound')) {
279
320
  // key not found, special case, don't log an error
280
321
  // always include "Not found" in error message
281
322
  err = new Error("Failed to fetch key: " + key + ": Not found");
@@ -285,37 +326,8 @@ module.exports = Class.create({
285
326
  // some other error
286
327
  self.logError('s3', "Failed to fetch key: " + key + ": " + (err.message || err), err);
287
328
  }
288
- callback( err, null );
289
- return;
290
- }
291
-
292
- var body = null;
293
- if (is_binary) {
294
- body = data.Body;
295
- self.logDebug(9, "Binary fetch complete: " + key, '' + body.length + ' bytes');
296
- }
297
- else {
298
- body = data.Body.toString();
299
-
300
- // possibly cache in LRU
301
- if (self.cache) {
302
- self.cache.set( orig_key, body, { date: Tools.timeNow(true) } );
303
- }
304
-
305
- try { body = JSON.parse( body ); }
306
- catch (e) {
307
- self.logError('s3', "Failed to parse JSON record: " + key + ": " + e);
308
- callback( e, null );
309
- return;
310
- }
311
- self.logDebug(9, "JSON fetch complete: " + key, self.debugLevel(10) ? body : null);
312
- }
313
-
314
- callback( null, body, {
315
- mod: Math.floor((new Date(data.LastModified)).getTime() / 1000),
316
- len: data.ContentLength
329
+ callback( err );
317
330
  } );
318
- } );
319
331
  },
320
332
 
321
333
  getStream: function(key, callback) {
@@ -326,27 +338,30 @@ module.exports = Class.create({
326
338
 
327
339
  this.logDebug(9, "Fetching S3 Stream: " + key);
328
340
 
329
- var params = { Key: this.extKey(key, orig_key) };
330
- var download = self.s3.getObject(params).createReadStream();
331
- var proceed = false;
332
-
333
- download.on('error', function(err) {
334
- if (proceed) self.logError('s3', "Failed to download key: " + key + ": " + (err.message || err), err);
335
- });
336
- download.once('end', function() {
337
- self.logDebug(9, "S3 stream download complete: " + key);
338
- } );
339
- download.once('close', function() {
340
- self.logDebug(9, "S3 stream download closed: " + key);
341
- } );
342
-
343
- // double-callback protection (bug in aws-sdk)
344
- var done = false;
345
- this.s3.headObject( params, function(err, data) {
346
- if (done) return; else done = true;
347
-
348
- if (err) {
349
- if ((err.code == 'NoSuchKey') || (err.code == 'NotFound')) {
341
+ var params = Tools.copyHash( this.s3Params );
342
+ params.Key = this.extKey(key, orig_key);
343
+
344
+ this.s3.send( new S3.GetObjectCommand(params) )
345
+ .then( function(data) {
346
+ var download = data.Body;
347
+
348
+ download.on('error', function(err) {
349
+ self.logError('s3', "Failed to download key: " + key + ": " + (err.message || err), err);
350
+ });
351
+ download.once('end', function() {
352
+ self.logDebug(9, "S3 stream download complete: " + key);
353
+ } );
354
+ download.once('close', function() {
355
+ self.logDebug(9, "S3 stream download closed: " + key);
356
+ } );
357
+
358
+ callback( null, download, {
359
+ mod: Math.floor( (new Date(data.LastModified)).getTime() / 1000 ),
360
+ len: data.ContentLength
361
+ } );
362
+ })
363
+ .catch( function(err) {
364
+ if ((err.name == 'NoSuchKey') || (err.name == 'NotFound') || (err.code == 'NoSuchKey') || (err.code == 'NotFound')) {
350
365
  // key not found, special case, don't log an error
351
366
  // always include "Not found" in error message
352
367
  err = new Error("Failed to fetch key: " + key + ": Not found");
@@ -356,18 +371,8 @@ module.exports = Class.create({
356
371
  // some other error
357
372
  self.logError('s3', "Failed to fetch key: " + key + ": " + (err.message || err), err);
358
373
  }
359
-
360
- download.destroy();
361
- callback( err, null );
362
- return;
363
- }
364
-
365
- proceed = true;
366
- callback( null, download, {
367
- mod: Math.floor((new Date(data.LastModified)).getTime() / 1000),
368
- len: data.ContentLength
369
- } );
370
- }); // headObject
374
+ callback( err );
375
+ });
371
376
  },
372
377
 
373
378
  getStreamRange: function(key, start, end, callback) {
@@ -378,7 +383,8 @@ module.exports = Class.create({
378
383
 
379
384
  this.logDebug(9, "Fetching ranged S3 stream: " + key, { start, end });
380
385
 
381
- var params = { Key: this.extKey(key, orig_key) };
386
+ var params = Tools.copyHash( this.s3Params );
387
+ params.Key = this.extKey(key, orig_key);
382
388
 
383
389
  // convert start/end to HTTP range header string
384
390
  var range = "bytes=";
@@ -386,26 +392,29 @@ module.exports = Class.create({
386
392
  range += '-';
387
393
  if (!isNaN(end)) range += end;
388
394
 
389
- var download = self.s3.getObject( Tools.mergeHashes(params, { Range: range }) ).createReadStream();
390
- var proceed = false;
391
-
392
- download.on('error', function(err) {
393
- if (proceed) self.logError('s3', "Failed to download key: " + key + ": " + (err.message || err), err);
394
- });
395
- download.once('end', function() {
396
- self.logDebug(9, "S3 stream download complete: " + key);
397
- } );
398
- download.once('close', function() {
399
- self.logDebug(9, "S3 stream download closed: " + key);
400
- } );
401
-
402
- // double-callback protection (bug in aws-sdk)
403
- var done = false;
404
- this.s3.headObject( params, function(err, data) {
405
- if (done) return; else done = true;
406
-
407
- if (err) {
408
- if ((err.code == 'NoSuchKey') || (err.code == 'NotFound')) {
395
+ params.Range = range;
396
+
397
+ this.s3.send( new S3.GetObjectCommand(params) )
398
+ .then( function(data) {
399
+ var download = data.Body;
400
+
401
+ download.on('error', function(err) {
402
+ self.logError('s3', "Failed to download key: " + key + ": " + (err.message || err), err);
403
+ });
404
+ download.once('end', function() {
405
+ self.logDebug(9, "S3 stream download complete: " + key);
406
+ } );
407
+ download.once('close', function() {
408
+ self.logDebug(9, "S3 stream download closed: " + key);
409
+ } );
410
+
411
+ callback( null, download, {
412
+ mod: Math.floor( (new Date(data.LastModified)).getTime() / 1000 ),
413
+ len: data.ContentLength
414
+ } );
415
+ })
416
+ .catch( function(err) {
417
+ if ((err.name == 'NoSuchKey') || (err.name == 'NotFound') || (err.code == 'NoSuchKey') || (err.code == 'NotFound')) {
409
418
  // key not found, special case, don't log an error
410
419
  // always include "Not found" in error message
411
420
  err = new Error("Failed to fetch key: " + key + ": Not found");
@@ -415,32 +424,8 @@ module.exports = Class.create({
415
424
  // some other error
416
425
  self.logError('s3', "Failed to fetch key: " + key + ": " + (err.message || err), err);
417
426
  }
418
-
419
- download.destroy();
420
- callback( err, null );
421
- return;
422
- }
423
-
424
- // validate byte range, now that we have the head info
425
- if (isNaN(start) && !isNaN(end)) {
426
- start = data.ContentLength - end;
427
- end = data.ContentLength ? data.ContentLength - 1 : 0;
428
- }
429
- else if (!isNaN(start) && isNaN(end)) {
430
- end = data.ContentLength ? data.ContentLength - 1 : 0;
431
- }
432
- if (isNaN(start) || isNaN(end) || (start < 0) || (start >= data.ContentLength) || (end < start) || (end >= data.ContentLength)) {
433
- download.destroy();
434
- callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + data.ContentLength + ")"), null );
435
- return;
436
- }
437
-
438
- proceed = true;
439
- callback( null, download, {
440
- mod: Math.floor((new Date(data.LastModified)).getTime() / 1000),
441
- len: data.ContentLength
442
- } );
443
- }); // headObject
427
+ callback( err );
428
+ });
444
429
  },
445
430
 
446
431
  delete: function(key, callback) {
@@ -451,23 +436,24 @@ module.exports = Class.create({
451
436
 
452
437
  this.logDebug(9, "Deleting S3 Object: " + key);
453
438
 
454
- // double-callback protection (bug in aws-sdk)
455
- var done = false;
456
- this.s3.deleteObject( { Key: this.extKey(key, orig_key) }, function(err, data) {
457
- if (done) return; else done = true;
458
-
459
- if (err) {
439
+ var params = Tools.copyHash( this.s3Params );
440
+ params.Key = this.extKey(key, orig_key);
441
+
442
+ this.s3.send( new S3.DeleteObjectCommand(params) )
443
+ .then( function(data) {
444
+ self.logDebug(9, "Delete complete: " + key);
445
+
446
+ // possibly delete from LRU cache as well
447
+ if (self.cache && self.cache.has(orig_key)) {
448
+ self.cache.delete(orig_key);
449
+ }
450
+
451
+ if (callback) callback(null, data);
452
+ } )
453
+ .catch( function(err) {
460
454
  self.logError('s3', "Failed to delete object: " + key + ": " + (err.message || err), err);
461
- }
462
- else self.logDebug(9, "Delete complete: " + key);
463
-
464
- // possibly delete from LRU cache as well
465
- if (self.cache && self.cache.has(orig_key)) {
466
- self.cache.delete(orig_key);
467
- }
468
-
469
- if (callback) callback(err, data);
470
- } );
455
+ if (callback) callback(err);
456
+ } );
471
457
  },
472
458
 
473
459
  runMaintenance: function(callback) {
@@ -59,7 +59,7 @@ module.exports = Class.create({
59
59
 
60
60
  if (value.match(/^(N?)(\d+)$/i)) {
61
61
  var neg = RegExp.$1.toUpperCase();
62
- var value = parseInt( RegExp.$2 );
62
+ var value = Math.floor( parseInt( RegExp.$2 ) * (def.multiply || 1) / (def.divide || 1) );
63
63
  value = Math.min( NUMBER_INDEX_MAX, value );
64
64
 
65
65
  var tkey = 'T' + neg + Math.floor( Math.floor(value / 1000) * 1000 );
@@ -103,7 +103,7 @@ module.exports = Class.create({
103
103
 
104
104
  // clean number up
105
105
  word = word.replace(/^N/i, '-').replace(/[^\d\-]+/g, '');
106
- word = '' + Math.min( NUMBER_INDEX_MAX, Math.max( NUMBER_INDEX_MIN, parseInt(word) ) );
106
+ word = '' + Math.min( NUMBER_INDEX_MAX, Math.max( NUMBER_INDEX_MIN, Math.floor( parseInt(word) * (query.def.multiply || 1) / (query.def.divide || 1) ) ) );
107
107
  word = word.replace(/\-/, 'N');
108
108
  query.word = word;
109
109
 
@@ -190,7 +190,7 @@ module.exports = Class.create({
190
190
 
191
191
  // clean number up
192
192
  word = word.replace(/^N/i, '-').replace(/[^\d\-]+/g, '');
193
- word = '' + Math.min( NUMBER_INDEX_MAX, Math.max( NUMBER_INDEX_MIN, parseInt(word) ) );
193
+ word = '' + Math.min( NUMBER_INDEX_MAX, Math.max( NUMBER_INDEX_MIN, Math.floor( parseInt(word) * (query.def.multiply || 1) / (query.def.divide || 1) ) ) );
194
194
  word = word.replace(/\-/, 'N');
195
195
  query.word = word;
196
196
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixl-server-storage",
3
- "version": "3.0.24",
3
+ "version": "3.1.0",
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",
@@ -21,17 +21,19 @@
21
21
  "redis"
22
22
  ],
23
23
  "dependencies": {
24
- "pixl-server": "^1.0.0",
25
- "pixl-class": "^1.0.3",
26
- "pixl-tools": "^1.0.24",
27
- "pixl-perf": "^1.0.0",
28
- "pixl-cache": "^1.0.1",
24
+ "@aws-sdk/client-s3": "3.186.0",
29
25
  "async": "2.6.4",
30
- "nearley": "2.20.1",
26
+ "fast-stream-to-buffer": "1.0.0",
27
+ "he": "1.1.1",
31
28
  "moo": "0.4.3",
29
+ "nearley": "2.20.1",
30
+ "pixl-cache": "^1.0.1",
31
+ "pixl-class": "^1.0.3",
32
+ "pixl-perf": "^1.0.0",
33
+ "pixl-server": "^1.0.0",
34
+ "pixl-tools": "^1.0.24",
32
35
  "porter-stemmer": "0.9.1",
33
- "unidecode": "0.1.8",
34
- "he": "1.1.1"
36
+ "unidecode": "0.1.8"
35
37
  },
36
38
  "devDependencies": {
37
39
  "pixl-unit": "^1.0.0"
package/test/test-main.js CHANGED
@@ -446,10 +446,11 @@ module.exports = {
446
446
 
447
447
  self.storage.getStreamRange( key, 0, 5, function(err, storageStream, streamInfo) {
448
448
  test.ok( !err, "No error fetching stream: " + err );
449
+ test.debug( "streamInfo: ", streamInfo );
449
450
  test.ok( !!storageStream, "Got storage stream as 2nd arg");
450
451
  test.ok( !!storageStream.pipe, "Storage stream has a pipe");
451
452
  test.ok( !!streamInfo, "Info was provided as the 3rd arg");
452
- test.ok( streamInfo.len == 43, "Info has correct data length");
453
+ test.ok( streamInfo.len == 6, "Info has correct data length");
453
454
  test.ok( streamInfo.mod > 0, "Info has a non-zero mod date");
454
455
 
455
456
  outStream.on('finish', function() {
@@ -496,7 +497,7 @@ module.exports = {
496
497
  test.ok( !!storageStream, "Got storage stream as 2nd arg");
497
498
  test.ok( !!storageStream.pipe, "Storage stream has a pipe");
498
499
  test.ok( !!streamInfo, "Info was provided as the 3rd arg");
499
- test.ok( streamInfo.len == 43, "Info has correct data length");
500
+ test.ok( streamInfo.len == 23, "Info has correct data length");
500
501
  test.ok( streamInfo.mod > 0, "Info has a non-zero mod date");
501
502
 
502
503
  outStream.on('finish', function() {
@@ -543,7 +544,7 @@ module.exports = {
543
544
  test.ok( !!storageStream, "Got storage stream as 2nd arg");
544
545
  test.ok( !!storageStream.pipe, "Storage stream has a pipe");
545
546
  test.ok( !!streamInfo, "Info was provided as the 3rd arg");
546
- test.ok( streamInfo.len == 43, "Info has correct data length");
547
+ test.ok( streamInfo.len == 10, "Info has correct data length");
547
548
  test.ok( streamInfo.mod > 0, "Info has a non-zero mod date");
548
549
 
549
550
  outStream.on('finish', function() {