pixl-server-storage 4.0.2 → 4.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
@@ -396,8 +396,8 @@ To enable the filesystem cache, include a `cache` object in your `Filesystem` co
396
396
  "base_dir": "/let/data/myserver",
397
397
  "cache": {
398
398
  "enabled": true,
399
- "maxItems": 1000,
400
- "maxBytes": 10485760
399
+ "maxItems": 100000,
400
+ "maxBytes": 104857600
401
401
  }
402
402
  }
403
403
  }
@@ -440,8 +440,8 @@ If you want to use [Amazon S3](http://aws.amazon.com/s3/) as a backing store, co
440
440
  },
441
441
  "cache": {
442
442
  "enabled": true,
443
- "maxItems": 1000,
444
- "maxBytes": 10485760
443
+ "maxItems": 100000,
444
+ "maxBytes": 104857600
445
445
  }
446
446
  }
447
447
  }
@@ -498,8 +498,8 @@ To enable the S3 cache, include a `cache` object in your `S3` configuration with
498
498
  {
499
499
  "cache": {
500
500
  "enabled": true,
501
- "maxItems": 1000,
502
- "maxBytes": 10485760
501
+ "maxItems": 100000,
502
+ "maxBytes": 104857600
503
503
  }
504
504
  }
505
505
  ```
@@ -564,8 +564,8 @@ Here is a complete example with the new parameters added:
564
564
  },
565
565
  "cache": {
566
566
  "enabled": true,
567
- "maxItems": 1000,
568
- "maxBytes": 10485760
567
+ "maxItems": 100000,
568
+ "maxBytes": 104857600
569
569
  }
570
570
  }
571
571
  }
@@ -668,6 +668,11 @@ Then configure your storage thusly:
668
668
  "compress": true,
669
669
  "keep": 7
670
670
  },
671
+ "cache": {
672
+ "enabled": true,
673
+ "maxItems": 100000,
674
+ "maxBytes": 104857600
675
+ },
671
676
  "keyPrefix": "",
672
677
  "keyTemplate": ""
673
678
  }
@@ -680,10 +685,61 @@ The optional `pragmas` object allows you set one or more [SQLite Pragmas](https:
680
685
 
681
686
  The optional `backups` object allows you to configure daily backups of the SQLite database file, which happens during the [daily maintenance](#daily-maintenance) event. Set `enabled` to `true` to enable backups, then set `dir` to the directory to hold the backups, `filename` to the destination filename (you can use date/time placeholders here), `compress` to compress the backups with gzip, and `keep` to keep the latest N backups in the dir (i.e. auto-delete the oldest ones). Please note that the backup operation locks the database, so it will cause other storage operations to hang while it is writing to the file.
682
687
 
688
+ See [S3 Cache](#s3-cache) for details on the `cache` section, as it works in the same way.
689
+
683
690
  The optional `keyPrefix` property works similarly to the [S3 Key Prefix](#s3-key-prefix) feature. It allows you to prefix all the SQLite keys with a common string, to separate your application's data in a shared database situation.
684
691
 
685
692
  The optional `keyTemplate` property works similarly to the [S3 Key Template](#s3-key-template) feature. It allows you to specify an exact layout of MD5 hash characters, which can be prefixed, mixed in with or postfixed after the key.
686
693
 
694
+ ## Postgres
695
+
696
+ If you want to use [Postgres](https://www.postgresql.org/) as a backing store, here is how to do so. First, you need to manually install the [pg](https://www.npmjs.com/package/pg) module into your app:
697
+
698
+ ```sh
699
+ npm install --save pg
700
+ ```
701
+
702
+ Then configure your storage thusly:
703
+
704
+ ```json
705
+ {
706
+ "engine": "Postgres",
707
+ "Postgres": {
708
+ "min": 0,
709
+ "max": 32,
710
+ "host": "localhost",
711
+ "database": "postgres",
712
+ "user": "postgres",
713
+ "password": "postgres",
714
+ "port": 5432,
715
+ "statement_timeout": 5000,
716
+ "query_timeout": 6000,
717
+ "connectionTimeoutMillis": 30000,
718
+ "idleTimeoutMillis": 10000,
719
+ "table": "items",
720
+ "cache": {
721
+ "enabled": true,
722
+ "maxItems": 100000,
723
+ "maxBytes": 104857600
724
+ }
725
+ }
726
+ }
727
+ ```
728
+
729
+ The Postgres engine uses a single table to act as the key/value store. The table is automatically created on startup if needed:
730
+
731
+ ```sql
732
+ CREATE TABLE IF NOT EXISTS items( key TEXT PRIMARY KEY, value BYTEA NOT NULL, modified BIGINT NOT NULL )
733
+ ```
734
+
735
+ You can customize the table name via the `table` configuration property. It defaults to simply `items`.
736
+
737
+ [Connection Pooling](https://node-postgres.com/features/pooling) is utilized to open many simultaneous connections to greatly increase throughput. Set the `max` property to the maximum number of concurrent connections your database can handle. Ideally this should be set to the global [concurrency](#concurrency) setting.
738
+
739
+ See [S3 Cache](#s3-cache) for details on the `cache` section, as it works in the same way.
740
+
741
+ Note that pooling and caching have little effect when the database is running locally (i.e. localhost). They offer much more benefit when the database is located remotely over some number of network hops.
742
+
687
743
  ## Hybrid
688
744
 
689
745
  Your application may need the features of multiple engines. Specifically, you may want JSON (document) records to use one engine, and binary records to use another. Binary records are specified with keys that end in a file extension, e.g. `.jpg`. To facilitate this, there is a `Hybrid` engine available, which can load multiple sub-engines, one for JSON keys and one for binary keys. Example use:
@@ -288,7 +288,6 @@ module.exports = Class.create({
288
288
  end = buf.length ? buf.length - 1 : 0;
289
289
  }
290
290
  if (isNaN(start) || isNaN(end) || (start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
291
- download.destroy();
292
291
  callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
293
292
  return;
294
293
  }
@@ -569,7 +569,6 @@ module.exports = Class.create({
569
569
  end = stats.size ? stats.size - 1 : 0;
570
570
  }
571
571
  if (isNaN(start) || isNaN(end) || (start < 0) || (start >= stats.size) || (end < start) || (end >= stats.size)) {
572
- download.destroy();
573
572
  callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + stats.size + ")"), null );
574
573
  return;
575
574
  }
@@ -0,0 +1,475 @@
1
+ // Postgres Storage Plugin
2
+ // Copyright (c) 2026 Joseph Huckaby
3
+ // Released under the MIT License
4
+
5
+ // Requires the 'pg' module
6
+
7
+ const fs = require('fs');
8
+ const Path = require('path');
9
+ const zlib = require('zlib');
10
+ const Class = require("pixl-class");
11
+ const Component = require("pixl-server/component");
12
+ const pg = require('pg');
13
+ const Tools = require("pixl-tools");
14
+ const Perf = require("pixl-perf");
15
+ const Cache = require("pixl-cache");
16
+ const async = require('async');
17
+ const noop = function() {};
18
+
19
+ module.exports = Class.create({
20
+
21
+ __name: 'Postgres',
22
+ __parent: Component,
23
+
24
+ defaultConfig: {
25
+ min: 0,
26
+ max: 32,
27
+ host: "localhost",
28
+ database: "",
29
+ user: "",
30
+ password: "",
31
+ port: 5432,
32
+ statement_timeout: 5000,
33
+ query_timeout: 6000,
34
+ connectionTimeoutMillis: 30000,
35
+ idleTimeoutMillis: 10000,
36
+ table: "items"
37
+ },
38
+
39
+ startup: function(callback) {
40
+ // setup Postgres connection
41
+ var self = this;
42
+ this.logDebug(2, "Setting up Postgres", Tools.copyHashRemoveKeys( this.config.get(), { password:1 }) );
43
+ this.setup(callback);
44
+ },
45
+
46
+ setup: function(callback) {
47
+ // setup Postgres connection
48
+ var self = this;
49
+ var pg_config = this.config.get();
50
+
51
+ this.commands = {
52
+ create: `CREATE TABLE IF NOT EXISTS ${pg_config.table}( key TEXT PRIMARY KEY, value BYTEA NOT NULL, modified BIGINT NOT NULL )`,
53
+ get: `SELECT modified, value FROM ${pg_config.table} WHERE key = $key LIMIT 1`,
54
+ head: `SELECT modified, octet_length(value) AS len FROM ${pg_config.table} WHERE key = $key LIMIT 1`,
55
+ put: `INSERT INTO ${pg_config.table} (key, value, modified) VALUES ($key, $value, $now) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value, modified = EXCLUDED.modified`,
56
+ delete: `DELETE FROM ${pg_config.table} WHERE key = $key`
57
+ };
58
+
59
+ // optional LRU cache
60
+ this.cache = null;
61
+ var cache_opts = pg_config.cache;
62
+ if (cache_opts && cache_opts.enabled) {
63
+ this.logDebug(3, "Setting up LRU cache", cache_opts);
64
+ this.cache = new Cache( Tools.copyHashRemoveKeys(cache_opts, { enabled: 1 }) );
65
+ this.cache.on('expire', function(item, reason) {
66
+ self.logDebug(9, "Expiring LRU cache object: " + item.key + " due to: " + reason, {
67
+ key: item.key,
68
+ reason: reason,
69
+ totalCount: self.cache.count,
70
+ totalBytes: self.cache.bytes
71
+ });
72
+ });
73
+ }
74
+
75
+ // pass entire config to pg.Pool, sans our custom keys
76
+ var db = this.db = new pg.Pool(
77
+ Tools.copyHashRemoveKeys( pg_config, { cache: 1, table: 1 })
78
+ );
79
+
80
+ db.on('error', function(err) {
81
+ self.logError('db', "DB Error: " + err, err);
82
+ if (callback) { callback(err); callback = null; }
83
+ });
84
+ db.on('connect', function(client) {
85
+ self.logDebug(9, "Opened DB pool connection");
86
+ });
87
+ db.on('remove', function(client) {
88
+ self.logDebug(9, "Closed DB pool connection");
89
+ });
90
+
91
+ // create initial table
92
+ this.logDebug(3, "Creating initial table: " + pg_config.table);
93
+ this.query( this.commands.create, {}, function(err, res) {
94
+ if (err) self.logError('db', "Failed to create initial table: " + err, err);
95
+ else self.logDebug(3, "Setup complete");
96
+ if (callback) { callback(err); callback = null; }
97
+ } );
98
+ },
99
+
100
+ query(sql, args, callback) {
101
+ // run and log db query, and error if applicable
102
+ // allow for named variables in sql, expand to numbered $ variables for pg
103
+ var self = this;
104
+
105
+ // perform $ placeholder substitution to populate sargs array
106
+ var sargs = [];
107
+ sql = sql.replace(/\$(\w+)/g, function(m_all, name) {
108
+ var value = args[name];
109
+ sargs.push(value);
110
+ value = '$' + sargs.length;
111
+ return value;
112
+ } );
113
+
114
+ this.db.query( sql, sargs, function(err, res) {
115
+ if (err) {
116
+ self.logError('db', "Failed to execute SQL: " + err, { sql, err });
117
+ return callback(err);
118
+ }
119
+
120
+ self.logDebug(9, "SQL query completed successfully", {
121
+ command: res.command || '',
122
+ rowCount: res.rowCount || 0
123
+ });
124
+
125
+ callback(null, res);
126
+ } ); // db.query
127
+ },
128
+
129
+ prepKey: function(key) {
130
+ // prepare key (no-op)
131
+ return key;
132
+ },
133
+
134
+ put: function(key, value, callback) {
135
+ // store key+value in Postgres
136
+ var self = this;
137
+ var now = Tools.timeNow(true);
138
+ key = this.prepKey(key);
139
+
140
+ var is_binary = this.storage.isBinaryKey(key);
141
+
142
+ if (is_binary) {
143
+ this.logDebug(9, "Storing Postgres Binary Object: " + key, '' + value.length + ' bytes');
144
+ }
145
+ else {
146
+ this.logDebug(9, "Storing Postgres JSON Object: " + key, this.debugLevel(10) ? value : null);
147
+ value = Buffer.from( JSON.stringify( value ) );
148
+ }
149
+
150
+ this.query( this.commands.put, { key: key, value: value, now: now }, function(err, res) {
151
+ if (err) {
152
+ err.message = "Failed to store object: " + key + ": " + err;
153
+ self.logError('pg', '' + err);
154
+ if (callback) callback(err);
155
+ return;
156
+ }
157
+
158
+ self.logDebug(9, "Store complete: " + key);
159
+ // possibly cache in LRU
160
+ if (self.cache && !is_binary) {
161
+ self.cache.set( key, value, { date: now } );
162
+ }
163
+ if (callback) callback(null);
164
+ } ); // query
165
+ },
166
+
167
+ putStream: function(key, inp, callback) {
168
+ // store key+value in Postgres using read stream
169
+ var self = this;
170
+
171
+ // The Postgres API has no stream support.
172
+ // So, we have to do this the RAM-hard way...
173
+
174
+ var chunks = [];
175
+ var done = false;
176
+ var finish = function(err) {
177
+ if (done) return;
178
+ done = true;
179
+ callback(err);
180
+ };
181
+
182
+ inp.on('error', function(err) {
183
+ err.message = "Failed to store stream: " + key + ": " + err;
184
+ self.logError('pg', '' + err);
185
+ finish(err);
186
+ } );
187
+ inp.on('data', function(chunk) {
188
+ if (done) return;
189
+ chunks.push( chunk );
190
+ } );
191
+ inp.on('end', function() {
192
+ if (done) return;
193
+ var buf = Buffer.concat(chunks);
194
+ self.put( key, buf, finish );
195
+ } );
196
+ },
197
+
198
+ head: function(key, callback) {
199
+ // head pg item given key
200
+ var self = this;
201
+ key = this.prepKey(key);
202
+
203
+ // check cache first
204
+ if (this.cache && this.cache.has(key)) {
205
+ var item = this.cache.getMeta(key);
206
+
207
+ process.nextTick( function() {
208
+ self.logDebug(9, "Cached head complete: " + key);
209
+ callback( null, {
210
+ mod: item.date,
211
+ len: item.value.length
212
+ } );
213
+ } );
214
+ return;
215
+ } // cache
216
+
217
+ this.query( this.commands.head, { key: key }, function(err, res) {
218
+ if (err) {
219
+ err.message = "Failed to head key: " + key + ": " + err;
220
+ self.logError('pg', '' + err);
221
+ callback(err);
222
+ return;
223
+ }
224
+
225
+ var row = res.rows[0];
226
+
227
+ if (!row) {
228
+ var err = new Error("Failed to head key: " + key + ": Not found");
229
+ err.code = "NoSuchKey";
230
+ return callback(err, null);
231
+ }
232
+ callback(null, { mod: parseInt(row.modified, 10), len: row.len });
233
+ } ); // query
234
+ },
235
+
236
+ get: function(key, callback) {
237
+ // fetch Postgres value given key
238
+ var self = this;
239
+ key = this.prepKey(key);
240
+
241
+ var is_binary = this.storage.isBinaryKey(key);
242
+
243
+ // check cache first
244
+ if (this.cache && !is_binary && this.cache.has(key)) {
245
+ var data = this.cache.get(key);
246
+
247
+ process.nextTick( function() {
248
+ try { data = JSON.parse( data.toString() ); }
249
+ catch (e) {
250
+ self.logError('pg', "Failed to parse JSON record: " + key + ": " + e);
251
+ callback( e, null );
252
+ return;
253
+ }
254
+ self.logDebug(9, "Cached JSON fetch complete: " + key, self.debugLevel(10) ? data : null);
255
+
256
+ callback( null, data );
257
+ } );
258
+ return;
259
+ } // cache
260
+
261
+ this.logDebug(9, "Fetching Postgres Object: " + key);
262
+
263
+ this.query( this.commands.get, { key: key }, function(err, res) {
264
+ if (err) {
265
+ err.message = "Failed to fetch key: " + key + ": " + err;
266
+ self.logError('pg', '' + err);
267
+ callback(err);
268
+ return;
269
+ }
270
+
271
+ var row = res.rows[0];
272
+
273
+ if (!row) {
274
+ var err = new Error("Failed to fetch key: " + key + ": Not found");
275
+ err.code = "NoSuchKey";
276
+ return callback(err, null);
277
+ }
278
+ if (is_binary) {
279
+ self.logDebug(9, "Binary fetch complete: " + key, '' + row.value.length + ' bytes');
280
+ return callback(null, row.value);
281
+ }
282
+ if (self.cache) {
283
+ self.cache.set( key, row.value, { date: parseInt(row.modified, 10) } );
284
+ }
285
+ var json = null;
286
+ try { json = JSON.parse( row.value.toString() ); }
287
+ catch (err) {
288
+ self.logError('pg', "Failed to parse JSON record: " + key + ": " + err);
289
+ return callback(err, null);
290
+ }
291
+ self.logDebug(9, "JSON fetch complete: " + key, self.debugLevel(10) ? json : null);
292
+ callback(null, json);
293
+ } ); // query
294
+ },
295
+
296
+ getBuffer: function(key, callback) {
297
+ // fetch Postgres buffer given key
298
+ var self = this;
299
+ key = this.prepKey(key);
300
+
301
+ this.logDebug(9, "Fetching Postgres Object: " + key);
302
+
303
+ this.query( this.commands.get, { key: key }, function(err, res) {
304
+ if (err) {
305
+ err.message = "Failed to fetch key: " + key + ": " + err;
306
+ self.logError('pg', '' + err);
307
+ callback(err);
308
+ return;
309
+ }
310
+
311
+ var row = res.rows[0];
312
+
313
+ if (!row) {
314
+ var err = new Error("Failed to fetch key: " + key + ": Not found");
315
+ err.code = "NoSuchKey";
316
+ return callback(err, null);
317
+ }
318
+ self.logDebug(9, "Binary fetch complete: " + key, '' + row.value.length + ' bytes');
319
+ callback(null, row.value);
320
+ } ); // query
321
+ },
322
+
323
+ getStream: function(key, callback) {
324
+ // get readable stream to record value given key
325
+ var self = this;
326
+
327
+ // The Postgres API has no stream support.
328
+ // So, we have to do this the RAM-hard way...
329
+
330
+ this.query( this.commands.get, { key: this.prepKey(key) }, function(err, res) {
331
+ if (err) {
332
+ // an actual error
333
+ err.message = "Failed to fetch key: " + key + ": " + err;
334
+ self.logError('pg', '' + err);
335
+ return callback(err);
336
+ }
337
+
338
+ var row = res.rows[0];
339
+
340
+ if (!row) {
341
+ // record not found
342
+ var err = new Error("Failed to fetch key: " + key + ": Not found");
343
+ err.code = "NoSuchKey";
344
+ return callback( err, null );
345
+ }
346
+
347
+ self.logDebug(9, "Binary fetch complete: " + key, '' + row.value.length + ' bytes');
348
+ var stream = new BufferStream(row.value);
349
+ callback(null, stream, { mod: parseInt(row.modified, 10), len: row.value.length });
350
+ } ); // query
351
+ },
352
+
353
+ getStreamRange: function(key, start, end, callback) {
354
+ // get readable stream to record value given key and range
355
+ var self = this;
356
+
357
+ // The Postgres API has no stream support.
358
+ // So, we have to do this the RAM-hard way...
359
+
360
+ this.query( this.commands.get, { key: this.prepKey(key) }, function(err, res) {
361
+ if (err) {
362
+ // an actual error
363
+ err.message = "Failed to fetch key: " + key + ": " + err;
364
+ self.logError('pg', '' + err);
365
+ return callback(err);
366
+ }
367
+
368
+ var row = res.rows[0];
369
+
370
+ if (!row) {
371
+ // record not found
372
+ var err = new Error("Failed to fetch key: " + key + ": Not found");
373
+ err.code = "NoSuchKey";
374
+ return callback( err, null );
375
+ }
376
+
377
+ var buf = row.value;
378
+
379
+ // validate byte range, now that we have the head info
380
+ if (isNaN(start) && !isNaN(end)) {
381
+ start = buf.length - end;
382
+ end = buf.length ? buf.length - 1 : 0;
383
+ }
384
+ else if (!isNaN(start) && isNaN(end)) {
385
+ end = buf.length ? buf.length - 1 : 0;
386
+ }
387
+ if (isNaN(start) || isNaN(end) || (start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
388
+ callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
389
+ return;
390
+ }
391
+
392
+ var range = buf.slice(start, end + 1);
393
+ var stream = new BufferStream(range);
394
+ callback(null, stream, { mod: parseInt(row.modified, 10), len: buf.length });
395
+ } ); // query
396
+ },
397
+
398
+ delete: function(key, callback) {
399
+ // delete Postgres key given key
400
+ var self = this;
401
+ key = this.prepKey(key);
402
+
403
+ this.logDebug(9, "Deleting Postgres Object: " + key);
404
+
405
+ this.query( this.commands.delete, { key: key }, function(err, res) {
406
+ if (err) {
407
+ self.logError('pg', "Failed to delete object: " + key + ": " + err);
408
+ callback(err);
409
+ return;
410
+ }
411
+
412
+ if (!res.rowCount) {
413
+ var err = new Error("Failed to delete object: " + key + ": Not found");
414
+ err.code = "NoSuchKey";
415
+ return callback(err);
416
+ }
417
+ self.logDebug(9, "Delete complete: " + key);
418
+ if (self.cache && self.cache.has(key)) {
419
+ self.cache.delete(key);
420
+ }
421
+ callback();
422
+ } ); // query
423
+ },
424
+
425
+ runMaintenance: function(callback) {
426
+ // run daily maintenance
427
+ callback();
428
+ },
429
+
430
+ shutdown: function(callback) {
431
+ // shutdown storage
432
+ var self = this;
433
+ this.logDebug(2, "Shutting down Postgres");
434
+
435
+ if (this.db) {
436
+ this.db.end( function(err) {
437
+ if (err) self.logError('pg', "Failed to shutdown database cleanly: " + err);
438
+ else self.logDebug(3, "Shutdown complete");
439
+ delete self.db;
440
+ callback();
441
+ } );
442
+ }
443
+ else {
444
+ this.logDebug(3, "Shutdown complete");
445
+ callback();
446
+ }
447
+ }
448
+
449
+ });
450
+
451
+ // Modified the following snippet from node-streamifier:
452
+ // Copyright (c) 2014 Gabriel Llamas, MIT Licensed
453
+
454
+ var util = require('util');
455
+ var stream = require('stream');
456
+
457
+ var BufferStream = function (object, options) {
458
+ if (object instanceof Buffer || typeof object === 'string') {
459
+ options = options || {};
460
+ stream.Readable.call(this, {
461
+ highWaterMark: options.highWaterMark,
462
+ encoding: options.encoding
463
+ });
464
+ } else {
465
+ stream.Readable.call(this, { objectMode: true });
466
+ }
467
+ this._object = object;
468
+ };
469
+
470
+ util.inherits(BufferStream, stream.Readable);
471
+
472
+ BufferStream.prototype._read = function () {
473
+ this.push(this._object);
474
+ this._object = null;
475
+ };
package/engines/Redis.js CHANGED
@@ -289,7 +289,6 @@ module.exports = Class.create({
289
289
  end = buf.length ? buf.length - 1 : 0;
290
290
  }
291
291
  if (isNaN(start) || isNaN(end) || (start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
292
- download.destroy();
293
292
  callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
294
293
  return;
295
294
  }
@@ -303,7 +303,6 @@ module.exports = Class.create({
303
303
  end = buf.length ? buf.length - 1 : 0;
304
304
  }
305
305
  if (isNaN(start) || isNaN(end) || (start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
306
- download.destroy();
307
306
  callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
308
307
  return;
309
308
  }
package/engines/SQLite.js CHANGED
@@ -153,6 +153,7 @@ module.exports = Class.create({
153
153
  err.message = "Failed to store object: " + key + ": " + err;
154
154
  self.logError('sqlite', '' + err);
155
155
  if (callback) callback(err);
156
+ return;
156
157
  }
157
158
 
158
159
  self.logDebug(9, "Store complete: " + key);
@@ -208,7 +209,7 @@ module.exports = Class.create({
208
209
  catch (err) {
209
210
  err.message = "Failed to head key: " + key + ": " + err;
210
211
  self.logError('sqlite', '' + err);
211
- callback(err);
212
+ return callback(err);
212
213
  }
213
214
 
214
215
  if (!row) {
@@ -256,6 +257,7 @@ module.exports = Class.create({
256
257
  err.message = "Failed to fetch key: " + key + ": " + err;
257
258
  self.logError('sqlite', '' + err);
258
259
  callback(err);
260
+ return;
259
261
  }
260
262
 
261
263
  if (!row) {
@@ -297,6 +299,7 @@ module.exports = Class.create({
297
299
  err.message = "Failed to fetch key: " + key + ": " + err;
298
300
  self.logError('sqlite', '' + err);
299
301
  callback(err);
302
+ return;
300
303
  }
301
304
 
302
305
  if (!row) {
@@ -365,7 +368,6 @@ module.exports = Class.create({
365
368
  end = buf.length ? buf.length - 1 : 0;
366
369
  }
367
370
  if (isNaN(start) || isNaN(end) || (start < 0) || (start >= buf.length) || (end < start) || (end >= buf.length)) {
368
- download.destroy();
369
371
  callback( new Error("Invalid byte range (" + start + '-' + end + ") for key: " + key + " (len: " + buf.length + ")"), null );
370
372
  return;
371
373
  }
@@ -391,6 +393,7 @@ module.exports = Class.create({
391
393
  catch (err) {
392
394
  self.logError('sqlite', "Failed to delete object: " + key + ": " + err);
393
395
  callback(err);
396
+ return;
394
397
  }
395
398
 
396
399
  if (!info.changes) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pixl-server-storage",
3
- "version": "4.0.2",
3
+ "version": "4.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",
@@ -34,7 +34,7 @@
34
34
  "pixl-class": "^1.0.3",
35
35
  "pixl-perf": "^1.0.9",
36
36
  "pixl-server": "^1.0.39",
37
- "pixl-tools": "^1.0.33",
37
+ "pixl-tools": "^2.0.2",
38
38
  "porter-stemmer": "0.9.1",
39
39
  "stream-meter": "1.0.4",
40
40
  "unidecode": "0.1.8"
package/test/config.json CHANGED
@@ -84,6 +84,25 @@
84
84
  "compress": true,
85
85
  "keep": 7
86
86
  }
87
+ },
88
+ "Postgres": {
89
+ "min": 0,
90
+ "max": 32,
91
+ "host": "localhost",
92
+ "database": "postgres",
93
+ "user": "postgres",
94
+ "password": "postgres",
95
+ "port": 5432,
96
+ "statement_timeout": 5000,
97
+ "query_timeout": 6000,
98
+ "connectionTimeoutMillis": 30000,
99
+ "idleTimeoutMillis": 10000,
100
+ "table": "items",
101
+ "cache": {
102
+ "enabled": true,
103
+ "maxItems": 100000,
104
+ "maxBytes": 104857600
105
+ }
87
106
  }
88
107
  }
89
108
  }