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 +12 -21
- package/engines/S3.js +191 -205
- package/index_types/Number.js +3 -3
- package/package.json +11 -9
- package/test/test-main.js +4 -3
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,
|
|
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
|
-
"
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
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 -
|
|
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
|
|
69
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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
|
-
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
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
|
-
|
|
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
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
|
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
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
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
|
|
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 =
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
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
|
-
|
|
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
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
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) {
|
package/index_types/Number.js
CHANGED
|
@@ -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
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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 ==
|
|
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 ==
|
|
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 ==
|
|
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() {
|