@blamejs/core 0.7.0 → 0.7.4

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.
@@ -36,6 +36,7 @@ var { Readable } = require("stream");
36
36
  var safeXml = require("../parsers/safe-xml");
37
37
  var sharedRequest = require("./http-request");
38
38
  var C = require("../constants");
39
+ var requestHelpers = require("../request-helpers");
39
40
  var { ObjectStoreError } = require("../framework-error");
40
41
  var time = require("../time");
41
42
  var safeUrl = require("../safe-url");
@@ -248,16 +249,51 @@ function create(config) {
248
249
  });
249
250
  }
250
251
 
251
- function get(key) {
252
+ // get(key, opts?) — opts forwarded to Azure as conditional / range
253
+ // headers (Range / x-ms-range, If-None-Match, If-Match,
254
+ // If-Modified-Since, If-Unmodified-Since). Returns the body buffer;
255
+ // operators wanting status + response headers call getResponse().
256
+ function get(key, opts) {
257
+ return getResponse(key, opts).then(function (r) { return r.body; });
258
+ }
259
+
260
+ function getStream(key, opts) { return Readable.from(get(key, opts)); }
261
+
262
+ function getResponse(key, opts) {
263
+ opts = opts || {};
252
264
  var url = _blobUrl(key);
253
- var headers = _signed("GET", url, {});
265
+ var extraHeaders = {};
266
+ if (opts.range) {
267
+ extraHeaders["x-ms-range"] = "bytes=" + opts.range.start + "-" + opts.range.end;
268
+ }
269
+ if (opts.ifNoneMatch) extraHeaders["If-None-Match"] = opts.ifNoneMatch;
270
+ if (opts.ifMatch) extraHeaders["If-Match"] = opts.ifMatch;
271
+ if (opts.ifModifiedSince) extraHeaders["If-Modified-Since"] = opts.ifModifiedSince;
272
+ if (opts.ifUnmodifiedSince) extraHeaders["If-Unmodified-Since"] = opts.ifUnmodifiedSince;
273
+ var headers = _signed("GET", url, extraHeaders);
254
274
  return _httpRequest("GET", url, headers, null, reqOpts).then(function (res) {
255
- return res.body;
275
+ return {
276
+ statusCode: res.statusCode,
277
+ body: res.body,
278
+ etag: res.headers && res.headers.etag,
279
+ lastModified: res.headers && res.headers["last-modified"]
280
+ ? Date.parse(res.headers["last-modified"]) : null,
281
+ contentRange: res.headers && res.headers["content-range"] || null,
282
+ size: res.headers && res.headers["content-length"]
283
+ ? parseInt(res.headers["content-length"], 10) : null,
284
+ contentType: res.headers && res.headers["content-type"] || null,
285
+ };
286
+ }, function (err) {
287
+ if (err && err.statusCode === requestHelpers.HTTP_STATUS.NOT_MODIFIED) {
288
+ return {
289
+ statusCode: requestHelpers.HTTP_STATUS.NOT_MODIFIED,
290
+ body: null, etag: null, lastModified: null,
291
+ };
292
+ }
293
+ throw err;
256
294
  });
257
295
  }
258
296
 
259
- function getStream(key) { return Readable.from(get(key)); }
260
-
261
297
  function head(key) {
262
298
  var url = _blobUrl(key);
263
299
  var headers = _signed("HEAD", url, {});
@@ -434,6 +470,7 @@ function create(config) {
434
470
  put: put,
435
471
  get: get,
436
472
  getStream: getStream,
473
+ getResponse: getResponse,
437
474
  head: head,
438
475
  delete: deleteKey,
439
476
  list: list,
@@ -28,6 +28,7 @@ var { Readable } = require("stream");
28
28
  var safeJson = require("../safe-json");
29
29
  var C = require("../constants");
30
30
  var nb = require("../numeric-bounds");
31
+ var requestHelpers = require("../request-helpers");
31
32
  var { ObjectStoreError } = require("../framework-error");
32
33
  var safeUrl = require("../safe-url");
33
34
  var sharedRequest = require("./http-request");
@@ -223,15 +224,53 @@ function create(config) {
223
224
  return { size: parseInt(meta.size || buf.length, 10), etag: meta.etag };
224
225
  }
225
226
 
226
- async function get(key) {
227
- var token = await _ensureToken();
228
- var url = _objectUrl(key, { alt: "media" });
229
- var res = await _httpRequest("GET", url, authHeader.bearer(token), null, reqOpts);
230
- return res.body;
227
+ // get(key, opts?) — opts forwarded to GCS as conditional / range
228
+ // headers (Range, If-None-Match, If-Match, If-Modified-Since,
229
+ // If-Unmodified-Since). Returns the body buffer for backwards compat;
230
+ // operators wanting full status + response headers call getResponse().
231
+ async function get(key, opts) {
232
+ var r = await getResponse(key, opts);
233
+ return r.body;
231
234
  }
232
235
 
233
- function getStream(key) {
234
- return Readable.from(get(key));
236
+ function getStream(key, opts) {
237
+ return Readable.from(get(key, opts));
238
+ }
239
+
240
+ async function getResponse(key, opts) {
241
+ opts = opts || {};
242
+ var token = await _ensureToken();
243
+ var url = _objectUrl(key, { alt: "media" });
244
+ var headers = authHeader.bearer(token);
245
+ if (opts.range) {
246
+ headers["Range"] = "bytes=" + opts.range.start + "-" + opts.range.end;
247
+ }
248
+ if (opts.ifNoneMatch) headers["If-None-Match"] = opts.ifNoneMatch;
249
+ if (opts.ifMatch) headers["If-Match"] = opts.ifMatch;
250
+ if (opts.ifModifiedSince) headers["If-Modified-Since"] = opts.ifModifiedSince;
251
+ if (opts.ifUnmodifiedSince) headers["If-Unmodified-Since"] = opts.ifUnmodifiedSince;
252
+ try {
253
+ var res = await _httpRequest("GET", url, headers, null, reqOpts);
254
+ return {
255
+ statusCode: res.statusCode,
256
+ body: res.body,
257
+ etag: res.headers && res.headers.etag,
258
+ lastModified: res.headers && res.headers["last-modified"]
259
+ ? Date.parse(res.headers["last-modified"]) : null,
260
+ contentRange: res.headers && res.headers["content-range"] || null,
261
+ size: res.headers && res.headers["content-length"]
262
+ ? parseInt(res.headers["content-length"], 10) : null,
263
+ contentType: res.headers && res.headers["content-type"] || null,
264
+ };
265
+ } catch (err) {
266
+ if (err && err.statusCode === requestHelpers.HTTP_STATUS.NOT_MODIFIED) {
267
+ return {
268
+ statusCode: requestHelpers.HTTP_STATUS.NOT_MODIFIED,
269
+ body: null, etag: null, lastModified: null,
270
+ };
271
+ }
272
+ throw err;
273
+ }
235
274
  }
236
275
 
237
276
  async function head(key) {
@@ -457,6 +496,7 @@ function create(config) {
457
496
  put: put,
458
497
  get: get,
459
498
  getStream: getStream,
499
+ getResponse: getResponse,
460
500
  head: head,
461
501
  delete: deleteKey,
462
502
  list: list,
@@ -30,6 +30,7 @@ var safeXml = require("../parsers/safe-xml");
30
30
  var sharedRequest = require("./http-request");
31
31
  var C = require("../constants");
32
32
  var nb = require("../numeric-bounds");
33
+ var requestHelpers = require("../request-helpers");
33
34
  var { ObjectStoreError } = require("../framework-error");
34
35
  var safeUrl = require("../safe-url");
35
36
 
@@ -593,18 +594,64 @@ function create(config) {
593
594
  }
594
595
  }
595
596
 
596
- function get(key) {
597
+ // get(key, opts?) — opts forwarded to the request as conditional /
598
+ // range headers so operator HTTP routes can pass If-None-Match,
599
+ // If-Match, If-Modified-Since, If-Unmodified-Since, and Range from the
600
+ // client request straight through to S3. Returns just the body buffer
601
+ // for backwards compatibility; operators wanting status + response
602
+ // headers (304 vs 206 vs 200) call getResponse() instead.
603
+ function get(key, opts) {
604
+ return getResponse(key, opts).then(function (r) { return r.body; });
605
+ }
606
+
607
+ function getStream(key, opts) {
608
+ return Readable.from(get(key, opts));
609
+ }
610
+
611
+ // getResponse(key, opts?) — full-fidelity GET. Returns
612
+ // { body, statusCode, etag, lastModified, contentRange, size,
613
+ // contentType }. Throws on non-2xx EXCEPT 304 (returned as
614
+ // { statusCode: 304, etag, lastModified, body: null }) so operator
615
+ // routes can short-circuit conditional GETs without losing the
616
+ // response headers.
617
+ function getResponse(key, opts) {
618
+ opts = opts || {};
597
619
  var url = _keyToUrl(key);
598
620
  var headers = _makeSigned("GET", url, sha256Hex(Buffer.alloc(0)));
599
- return _request("GET", url, headers, null, reqOpts).then(function (res) {
600
- return res.body;
621
+ if (opts.range) {
622
+ headers["Range"] = "bytes=" + opts.range.start + "-" + opts.range.end;
623
+ }
624
+ if (opts.ifNoneMatch) headers["If-None-Match"] = opts.ifNoneMatch;
625
+ if (opts.ifMatch) headers["If-Match"] = opts.ifMatch;
626
+ if (opts.ifModifiedSince) headers["If-Modified-Since"] = opts.ifModifiedSince;
627
+ if (opts.ifUnmodifiedSince) headers["If-Unmodified-Since"] = opts.ifUnmodifiedSince;
628
+ var localReqOpts = Object.assign({}, reqOpts, { _resolveOnRedirect: false });
629
+ return _request("GET", url, headers, null, localReqOpts).then(function (res) {
630
+ return {
631
+ statusCode: res.statusCode,
632
+ body: res.body,
633
+ etag: res.headers && res.headers.etag,
634
+ lastModified: res.headers && res.headers["last-modified"]
635
+ ? Date.parse(res.headers["last-modified"]) : null,
636
+ contentRange: res.headers && res.headers["content-range"] || null,
637
+ size: res.headers && res.headers["content-length"]
638
+ ? parseInt(res.headers["content-length"], 10) : null,
639
+ contentType: res.headers && res.headers["content-type"] || null,
640
+ };
641
+ }, function (err) {
642
+ // 304 surfaces as a "non-2xx error" via httpClient; propagate it
643
+ // as a structured 304 result instead so operator routes get
644
+ // the conditional-GET short-circuit they expect.
645
+ if (err && err.statusCode === requestHelpers.HTTP_STATUS.NOT_MODIFIED) {
646
+ return {
647
+ statusCode: requestHelpers.HTTP_STATUS.NOT_MODIFIED,
648
+ body: null, etag: null, lastModified: null,
649
+ };
650
+ }
651
+ throw err;
601
652
  });
602
653
  }
603
654
 
604
- function getStream(key) {
605
- return Readable.from(get(key));
606
- }
607
-
608
655
  function head(key) {
609
656
  var url = _keyToUrl(key);
610
657
  var headers = _makeSigned("HEAD", url, sha256Hex(Buffer.alloc(0)));
@@ -829,6 +876,7 @@ function create(config) {
829
876
  put: put,
830
877
  get: get,
831
878
  getStream: getStream,
879
+ getResponse: getResponse,
832
880
  head: head,
833
881
  delete: deleteKey,
834
882
  list: list,
@@ -41,17 +41,7 @@ function create(opts) {
41
41
  // double-fire local handlers).
42
42
  var instanceNonce = fwCrypto.generateToken(C.BYTES.bytes(8));
43
43
 
44
- var clientOpts = {
45
- url: opts.redisUrl,
46
- password: opts.redisPassword,
47
- username: opts.redisUsername,
48
- tls: opts.redisTls,
49
- ca: opts.redisCa,
50
- servername: opts.redisServername,
51
- connectTimeoutMs: opts.redisConnectTimeoutMs,
52
- commandTimeoutMs: opts.redisCommandTimeoutMs,
53
- maxReconnectAttempts: opts.redisMaxReconnectAttempts,
54
- };
44
+ var clientOpts = redisClient.pickClientOpts(opts, "redis");
55
45
 
56
46
  var subscriberConn = null;
57
47
  var publisherConn = null;
@@ -254,14 +254,7 @@ function create(opts) {
254
254
  var prefix = typeof opts.keyPrefix === "string" && opts.keyPrefix.length > 0
255
255
  ? opts.keyPrefix : DEFAULT_PREFIX;
256
256
 
257
- var client = redisClient.create({
258
- url: opts.url,
259
- password: opts.password,
260
- username: opts.username,
261
- tls: opts.tls,
262
- connectTimeoutMs: opts.connectTimeoutMs,
263
- commandTimeoutMs: opts.commandTimeoutMs,
264
- });
257
+ var client = redisClient.create(redisClient.pickClientOpts(opts));
265
258
 
266
259
  // Lazy connect — defer first connect until the first operation so
267
260
  // queue.init({ backends }) doesn't have to be async.
@@ -479,8 +479,38 @@ function _parseRedisUrl(s) {
479
479
  };
480
480
  }
481
481
 
482
+ // pickClientOpts(cfg, prefix?) — extract the standard redis-client opts
483
+ // from a larger config bag. Lets cache-redis / pubsub-redis / queue-redis
484
+ // / etc. forward to redisClient.create without each repeating the 9-key
485
+ // list. The optional `prefix` lets callers whose operator-facing opts
486
+ // are namespaced (`redisUrl`, `redisPassword`, ...) reuse the same
487
+ // helper by passing prefix="redis" — the helper camel-cases the prefix
488
+ // onto each key.
489
+ //
490
+ // var opts = redisClient.pickClientOpts(cfg); // unprefixed
491
+ // var opts = redisClient.pickClientOpts(operatorOpts, "redis"); // redisUrl etc.
492
+ function pickClientOpts(cfg, prefix) {
493
+ if (!cfg || typeof cfg !== "object") return {};
494
+ function pick(name) {
495
+ if (!prefix) return cfg[name];
496
+ return cfg[prefix + name.charAt(0).toUpperCase() + name.slice(1)];
497
+ }
498
+ return {
499
+ url: pick("url"),
500
+ password: pick("password"),
501
+ username: pick("username"),
502
+ tls: pick("tls"),
503
+ ca: pick("ca"),
504
+ servername: pick("servername"),
505
+ connectTimeoutMs: pick("connectTimeoutMs"),
506
+ commandTimeoutMs: pick("commandTimeoutMs"),
507
+ maxReconnectAttempts: pick("maxReconnectAttempts"),
508
+ };
509
+ }
510
+
482
511
  module.exports = {
483
512
  create: create,
513
+ pickClientOpts: pickClientOpts,
484
514
  // Exposed for tests / direct callers that already manage their own socket.
485
515
  _encodeCommand: _encodeCommand,
486
516
  _parseFrame: _parseFrame,
@@ -41,23 +41,27 @@
41
41
  // every consumer reads HTTP_STATUS.<NAME> rather than the underlying
42
42
  // integer, so the hex form is purely an internal storage detail.
43
43
  var HTTP_STATUS = Object.freeze({
44
- OK: 0xC8,
45
- NO_CONTENT: 0xCC,
46
- NOT_MODIFIED: 0x130,
47
- BAD_REQUEST: 0x190,
48
- UNAUTHORIZED: 0x191,
49
- FORBIDDEN: 0x193,
50
- NOT_FOUND: 0x194,
51
- METHOD_NOT_ALLOWED: 0x195,
52
- CONFLICT: 0x199,
53
- PAYLOAD_TOO_LARGE: 0x19D,
54
- UNSUPPORTED_MEDIA_TYPE: 0x19F,
55
- UNPROCESSABLE_CONTENT: 0x1A6,
56
- TOO_MANY_REQUESTS: 0x1AD,
57
- INTERNAL_SERVER_ERROR: 0x1F4,
58
- BAD_GATEWAY: 0x1F6,
59
- SERVICE_UNAVAILABLE: 0x1F7,
60
- GATEWAY_TIMEOUT: 0x1F8,
44
+ OK: 0xC8,
45
+ PARTIAL_CONTENT: 0xCE,
46
+ NO_CONTENT: 0xCC,
47
+ NOT_MODIFIED: 0x130,
48
+ BAD_REQUEST: 0x190,
49
+ UNAUTHORIZED: 0x191,
50
+ FORBIDDEN: 0x193,
51
+ NOT_FOUND: 0x194,
52
+ METHOD_NOT_ALLOWED: 0x195,
53
+ CONFLICT: 0x199,
54
+ PAYLOAD_TOO_LARGE: 0x19D,
55
+ UNSUPPORTED_MEDIA_TYPE: 0x19F,
56
+ RANGE_NOT_SATISFIABLE: 0x1A0,
57
+ UNPROCESSABLE_CONTENT: 0x1A6,
58
+ PRECONDITION_FAILED: 0x19C,
59
+ TOO_MANY_REQUESTS: 0x1AD,
60
+ UNAVAILABLE_FOR_LEGAL_REASONS: 0x1C3,
61
+ INTERNAL_SERVER_ERROR: 0x1F4,
62
+ BAD_GATEWAY: 0x1F6,
63
+ SERVICE_UNAVAILABLE: 0x1F7,
64
+ GATEWAY_TIMEOUT: 0x1F8,
61
65
  });
62
66
 
63
67
  // extractActorContext(req) — pull the 5 W's from a request for audit
package/lib/seeders.js CHANGED
@@ -137,11 +137,9 @@ function _validateEnv(name, value) {
137
137
  function _validateCreateOpts(opts) {
138
138
  validateOpts.requireObject(opts, "seeders.create", SeederError);
139
139
  validateOpts.requireNonEmptyString(opts.dir, "seeders.create: dir", SeederError, "BAD_OPT");
140
- if (opts.db !== undefined && opts.db !== null) {
141
- if (typeof opts.db !== "object" || typeof opts.db.prepare !== "function") {
142
- throw _err("BAD_OPT", "seeders.create: db must be a SQLite-shaped handle (prepare fn)");
143
- }
144
- }
140
+ validateOpts.optionalObjectWithMethod(opts.db, "prepare",
141
+ "seeders.create: db", SeederError, "BAD_OPT",
142
+ "must be a SQLite-shaped handle (prepare fn)");
145
143
  validateOpts.auditShape(opts.audit, "seeders.create", SeederError);
146
144
  validateOpts.optionalBoolean(opts.auditApplied, "seeders.create: auditApplied", SeederError);
147
145
  validateOpts.optionalBoolean(opts.auditFailures, "seeders.create: auditFailures", SeederError);
@@ -205,18 +203,8 @@ function _loadSeed(rootDir, env, file) {
205
203
  throw _err("BAD_SEED",
206
204
  "seed '" + env + "/" + file + "': rerunnable must be a boolean");
207
205
  }
208
- if (mod.dependsOn !== undefined) {
209
- if (!Array.isArray(mod.dependsOn)) {
210
- throw _err("BAD_SEED",
211
- "seed '" + env + "/" + file + "': dependsOn must be an array of seed filenames");
212
- }
213
- for (var j = 0; j < mod.dependsOn.length; j++) {
214
- if (typeof mod.dependsOn[j] !== "string" || mod.dependsOn[j].length === 0) {
215
- throw _err("BAD_SEED",
216
- "seed '" + env + "/" + file + "': dependsOn[" + j + "] must be a non-empty string");
217
- }
218
- }
219
- }
206
+ validateOpts.optionalNonEmptyStringArray(mod.dependsOn,
207
+ "seed '" + env + "/" + file + "': dependsOn", SeederError, "BAD_SEED");
220
208
  if (mod.description !== undefined && typeof mod.description !== "string") {
221
209
  throw _err("BAD_SEED",
222
210
  "seed '" + env + "/" + file + "': description must be a string");