pixl-server-storage 3.1.18 → 3.2.1
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 +146 -134
- package/docs/API.md +149 -144
- package/docs/Hashes.md +20 -20
- package/docs/Indexer.md +11 -14
- package/docs/Lists.md +37 -37
- package/docs/Transactions.md +11 -11
- package/engines/Redis.js +39 -36
- package/engines/RedisCluster.js +10 -13
- package/engines/S3.js +88 -0
- package/package.json +1 -1
package/engines/Redis.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
// Redis Storage Plugin
|
|
2
|
-
// Copyright (c) 2015 -
|
|
2
|
+
// Copyright (c) 2015 - 2024 Joseph Huckaby
|
|
3
3
|
// Released under the MIT License
|
|
4
4
|
|
|
5
|
-
// Requires the '
|
|
6
|
-
// npm install
|
|
5
|
+
// Requires the 'ioredis' module from npm
|
|
6
|
+
// npm install --save ioredis
|
|
7
7
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
const Class = require("pixl-class");
|
|
9
|
+
const Component = require("pixl-server/component");
|
|
10
|
+
const Redis = require('ioredis');
|
|
11
|
+
const Tools = require("pixl-tools");
|
|
12
12
|
|
|
13
13
|
module.exports = Class.create({
|
|
14
14
|
|
|
@@ -16,8 +16,14 @@ module.exports = Class.create({
|
|
|
16
16
|
__parent: Component,
|
|
17
17
|
|
|
18
18
|
defaultConfig: {
|
|
19
|
-
|
|
19
|
+
|
|
20
|
+
host: 'localhost',
|
|
20
21
|
port: 6379,
|
|
22
|
+
commandTimeout: 5000,
|
|
23
|
+
connectTimeout: 5000,
|
|
24
|
+
username: "",
|
|
25
|
+
password: "",
|
|
26
|
+
|
|
21
27
|
keyPrefix: "",
|
|
22
28
|
keyTemplate: ""
|
|
23
29
|
},
|
|
@@ -25,10 +31,7 @@ module.exports = Class.create({
|
|
|
25
31
|
startup: function(callback) {
|
|
26
32
|
// setup Redis connection
|
|
27
33
|
var self = this;
|
|
28
|
-
|
|
29
|
-
this.logDebug(2, "Setting up Redis",
|
|
30
|
-
Tools.copyHashRemoveKeys( this.config.get(), { password:1 }) );
|
|
31
|
-
|
|
34
|
+
this.logDebug(2, "Setting up Redis", this.config.get() );
|
|
32
35
|
this.setup(callback);
|
|
33
36
|
},
|
|
34
37
|
|
|
@@ -39,35 +42,31 @@ module.exports = Class.create({
|
|
|
39
42
|
|
|
40
43
|
this.keyPrefix = (r_config.keyPrefix || '').replace(/^\//, '');
|
|
41
44
|
if (this.keyPrefix && !this.keyPrefix.match(/\/$/)) this.keyPrefix += '/';
|
|
45
|
+
delete r_config.keyPrefix;
|
|
42
46
|
|
|
43
47
|
this.keyTemplate = (r_config.keyTemplate || '').replace(/^\//, '').replace(/\/$/, '');
|
|
48
|
+
delete r_config.keyTemplate;
|
|
44
49
|
|
|
45
|
-
r_config.
|
|
46
|
-
r_config.
|
|
47
|
-
// simple backoff strategy
|
|
48
|
-
return Math.min(opts.attempt * 100, 3000);
|
|
49
|
-
};
|
|
50
|
+
if (!r_config.username.length) delete r_config.username;
|
|
51
|
+
if (!r_config.password.length) delete r_config.password;
|
|
50
52
|
|
|
51
|
-
|
|
53
|
+
r_config.lazyConnect = true;
|
|
54
|
+
r_config.reconnectOnError = function(err) { return true; };
|
|
55
|
+
|
|
56
|
+
this.redis = new Redis(r_config);
|
|
52
57
|
|
|
53
58
|
this.redis.on('error', function(err) {
|
|
54
|
-
if (!self.storage.started)
|
|
59
|
+
if (!self.storage.started) {
|
|
60
|
+
return callback( new Error("Redis Startup Error: " + (err.message || err)) );
|
|
61
|
+
}
|
|
55
62
|
|
|
56
63
|
// error after startup? Just log it I guess
|
|
57
64
|
self.logError('redis', ''+err);
|
|
58
|
-
});
|
|
65
|
+
}); // error
|
|
59
66
|
|
|
60
|
-
this.redis.
|
|
61
|
-
self.logDebug(
|
|
62
|
-
|
|
63
|
-
});
|
|
64
|
-
|
|
65
|
-
this.redis.on('reconnecting', function(opts) {
|
|
66
|
-
self.logDebug(3, "Redis is reconnecting", opts);
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
this.redis.on('end', function() {
|
|
70
|
-
self.logDebug(3, "Redis disconnected");
|
|
67
|
+
this.redis.connect(function() {
|
|
68
|
+
self.logDebug(8, "Successfully connected to Redis");
|
|
69
|
+
callback();
|
|
71
70
|
});
|
|
72
71
|
},
|
|
73
72
|
|
|
@@ -167,7 +166,8 @@ module.exports = Class.create({
|
|
|
167
166
|
|
|
168
167
|
this.logDebug(9, "Fetching Redis Object: " + key);
|
|
169
168
|
|
|
170
|
-
this.
|
|
169
|
+
var func = this.storage.isBinaryKey(key) ? 'getBuffer' : 'get';
|
|
170
|
+
this.redis[func]( key, function(err, result) {
|
|
171
171
|
if (!result) {
|
|
172
172
|
if (err) {
|
|
173
173
|
// an actual error
|
|
@@ -204,13 +204,13 @@ module.exports = Class.create({
|
|
|
204
204
|
},
|
|
205
205
|
|
|
206
206
|
getBuffer: function(key, callback) {
|
|
207
|
-
//
|
|
207
|
+
// fetch Redis buffer given key
|
|
208
208
|
var self = this;
|
|
209
209
|
key = this.prepKey(key);
|
|
210
210
|
|
|
211
211
|
this.logDebug(9, "Fetching Redis Object: " + key);
|
|
212
212
|
|
|
213
|
-
this.redis.
|
|
213
|
+
this.redis.getBuffer( key, function(err, result) {
|
|
214
214
|
if (!result) {
|
|
215
215
|
if (err) {
|
|
216
216
|
// an actual error
|
|
@@ -329,8 +329,11 @@ module.exports = Class.create({
|
|
|
329
329
|
shutdown: function(callback) {
|
|
330
330
|
// shutdown storage
|
|
331
331
|
this.logDebug(2, "Shutting down Redis");
|
|
332
|
-
if (this.redis)
|
|
333
|
-
|
|
332
|
+
if (this.redis) {
|
|
333
|
+
this.redis.quit(callback);
|
|
334
|
+
this.redis = null;
|
|
335
|
+
}
|
|
336
|
+
else callback();
|
|
334
337
|
}
|
|
335
338
|
|
|
336
339
|
});
|
package/engines/RedisCluster.js
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
// RedisCluster Storage Plugin
|
|
2
|
-
// Copyright (c) 2015 -
|
|
2
|
+
// Copyright (c) 2015 - 2024 Joseph Huckaby
|
|
3
3
|
// Released under the MIT License
|
|
4
4
|
|
|
5
|
-
// Requires the 'ioredis'
|
|
6
|
-
// npm install --save ioredis
|
|
5
|
+
// Requires the 'ioredis' module from npm
|
|
6
|
+
// npm install --save ioredis
|
|
7
7
|
|
|
8
8
|
const Class = require("pixl-class");
|
|
9
9
|
const Component = require("pixl-server/component");
|
|
10
10
|
const Redis = require('ioredis');
|
|
11
|
-
const RedisTimeout = require('ioredis-timeout');
|
|
12
11
|
const Tools = require("pixl-tools");
|
|
13
12
|
|
|
14
13
|
module.exports = Class.create({
|
|
@@ -20,10 +19,13 @@ module.exports = Class.create({
|
|
|
20
19
|
|
|
21
20
|
host: 'localhost',
|
|
22
21
|
port: 6379,
|
|
23
|
-
timeout: 1000,
|
|
24
22
|
connectRetries: 5,
|
|
25
23
|
clusterOpts: {
|
|
26
|
-
scaleReads: "master"
|
|
24
|
+
scaleReads: "master",
|
|
25
|
+
redisOptions: {
|
|
26
|
+
commandTimeout: 5000,
|
|
27
|
+
connectTimeout: 5000
|
|
28
|
+
}
|
|
27
29
|
},
|
|
28
30
|
|
|
29
31
|
keyPrefix: "",
|
|
@@ -33,7 +35,7 @@ module.exports = Class.create({
|
|
|
33
35
|
startup: function(callback) {
|
|
34
36
|
// setup Redis connection
|
|
35
37
|
var self = this;
|
|
36
|
-
this.logDebug(2, "Setting up
|
|
38
|
+
this.logDebug(2, "Setting up RedisCluster", this.config.get() );
|
|
37
39
|
this.setup(callback);
|
|
38
40
|
},
|
|
39
41
|
|
|
@@ -77,12 +79,7 @@ module.exports = Class.create({
|
|
|
77
79
|
}); // error
|
|
78
80
|
|
|
79
81
|
this.redis.once('ready', function() {
|
|
80
|
-
self.logDebug(8, "Successfully connected to Redis");
|
|
81
|
-
|
|
82
|
-
if (r_config.timeout) {
|
|
83
|
-
RedisTimeout(self.redis, parseInt(r_config.timeout));
|
|
84
|
-
}
|
|
85
|
-
|
|
82
|
+
self.logDebug(8, "Successfully connected to Redis cluster");
|
|
86
83
|
callback();
|
|
87
84
|
});
|
|
88
85
|
},
|
package/engines/S3.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// Copyright (c) 2015 - 2022 Joseph Huckaby
|
|
3
3
|
// Released under the MIT License
|
|
4
4
|
|
|
5
|
+
var Path = require('path');
|
|
5
6
|
var Class = require("pixl-class");
|
|
6
7
|
var Component = require("pixl-server/component");
|
|
7
8
|
var Tools = require("pixl-tools");
|
|
@@ -596,6 +597,93 @@ module.exports = Class.create({
|
|
|
596
597
|
} );
|
|
597
598
|
},
|
|
598
599
|
|
|
600
|
+
list: function(opts, callback) {
|
|
601
|
+
// generate list of objects in S3 given prefix
|
|
602
|
+
// this repeatedly calls ListObjectsV2 for lists > 1000
|
|
603
|
+
// opts: { remotePath, filespec, filter }
|
|
604
|
+
// result: { files([{ key, size, mtime }, ...]), total_bytes }
|
|
605
|
+
let self = this;
|
|
606
|
+
let done = false;
|
|
607
|
+
let files = [];
|
|
608
|
+
let total_bytes = 0;
|
|
609
|
+
let num_calls = 0;
|
|
610
|
+
let now = Tools.timeNow(true);
|
|
611
|
+
|
|
612
|
+
if (typeof(opts) == 'string') opts = { remotePath: opts };
|
|
613
|
+
if (!opts.remotePath) opts.remotePath = '';
|
|
614
|
+
if (!opts.filespec) opts.filespec = /.*/;
|
|
615
|
+
if (!opts.filter) opts.filter = function() { return true; };
|
|
616
|
+
|
|
617
|
+
if (opts.older) {
|
|
618
|
+
// convert older to filter func with mtime
|
|
619
|
+
if (typeof(opts.older) == 'string') opts.older = Tools.getSecondsFromText( opts.older );
|
|
620
|
+
opts.filter = function(file) { return file.mtime <= now - opts.older; };
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
let params = Tools.mergeHashes( this.s3Params || {}, opts.params || {} );
|
|
624
|
+
params.Prefix = this.keyPrefix + opts.remotePath;
|
|
625
|
+
params.MaxKeys = 1000;
|
|
626
|
+
|
|
627
|
+
let PREFIX_RE = new RegExp('^' + Tools.escapeRegExp(this.keyPrefix));
|
|
628
|
+
|
|
629
|
+
this.logDebug(8, "Listing S3 files with prefix: " + params.Prefix, opts);
|
|
630
|
+
let tracker = this.perf ? this.perf.begin('s3_list') : null;
|
|
631
|
+
|
|
632
|
+
Tools.async.whilst(
|
|
633
|
+
function() {
|
|
634
|
+
return !done;
|
|
635
|
+
},
|
|
636
|
+
function(callback) {
|
|
637
|
+
self.logDebug(9, "Listing chunk", params);
|
|
638
|
+
|
|
639
|
+
self.s3.send( new S3.ListObjectsV2Command(params) )
|
|
640
|
+
.then( function(data) {
|
|
641
|
+
let items = data.Contents || [];
|
|
642
|
+
for (let idx = 0, len = items.length; idx < len; idx++) {
|
|
643
|
+
let item = items[idx];
|
|
644
|
+
let key = item.Key.replace(PREFIX_RE, '');
|
|
645
|
+
let bytes = item.Size;
|
|
646
|
+
let mtime = item.LastModified.getTime() / 1000;
|
|
647
|
+
let file = { key: key, size: bytes, mtime: mtime };
|
|
648
|
+
|
|
649
|
+
// optional filter and filespec
|
|
650
|
+
if (opts.filter(file) && Path.basename(key).match(opts.filespec)) {
|
|
651
|
+
total_bytes += bytes;
|
|
652
|
+
files.push(file);
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
// check for end of key list
|
|
657
|
+
if (!data.IsTruncated || !items.length) done = true;
|
|
658
|
+
else {
|
|
659
|
+
// advance to next chunk
|
|
660
|
+
params.StartAfter = items[ items.length - 1 ].Key;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
num_calls++;
|
|
664
|
+
callback();
|
|
665
|
+
} )
|
|
666
|
+
.catch( function(err) {
|
|
667
|
+
callback( err );
|
|
668
|
+
} );
|
|
669
|
+
},
|
|
670
|
+
function(err) {
|
|
671
|
+
if (tracker) tracker.end();
|
|
672
|
+
if (err) return process.nextTick( function() { callback(err, null, null); });
|
|
673
|
+
|
|
674
|
+
self.logDebug(9, "S3 listing complete (" + Tools.commify(files.length) + " objects, " + Tools.getTextFromBytes(total_bytes) + ")", {
|
|
675
|
+
prefix: params.Prefix,
|
|
676
|
+
count: files.length,
|
|
677
|
+
bytes: total_bytes,
|
|
678
|
+
calls: num_calls
|
|
679
|
+
});
|
|
680
|
+
|
|
681
|
+
// break out of promise context
|
|
682
|
+
process.nextTick( function() { callback( null, files, total_bytes ); } );
|
|
683
|
+
}
|
|
684
|
+
); // whilst
|
|
685
|
+
},
|
|
686
|
+
|
|
599
687
|
runMaintenance: function(callback) {
|
|
600
688
|
// run daily maintenance
|
|
601
689
|
callback();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pixl-server-storage",
|
|
3
|
-
"version": "3.1
|
|
3
|
+
"version": "3.2.1",
|
|
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",
|