spice-couchbase 2.0.62 → 2.0.63
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/build/connectionHealth.js +573 -0
- package/build/connectionHealth.test.js +659 -0
- package/build/index.js +219 -41
- package/package.json +3 -2
- package/src/connectionHealth.js +529 -0
- package/src/connectionHealth.test.js +412 -0
- package/src/index.js +198 -55
package/build/index.js
CHANGED
|
@@ -29,6 +29,11 @@ var {
|
|
|
29
29
|
extractStatementResources
|
|
30
30
|
} = require("./utility");
|
|
31
31
|
|
|
32
|
+
var {
|
|
33
|
+
createHealthMonitor,
|
|
34
|
+
openHandles
|
|
35
|
+
} = require("./connectionHealth");
|
|
36
|
+
|
|
32
37
|
if (!Promise.allSettled) {
|
|
33
38
|
Promise.allSettled = promises => Promise.all(promises.map((promise, i) => promise.then(value => ({
|
|
34
39
|
status: "fulfilled",
|
|
@@ -74,7 +79,26 @@ var optimizeState = {
|
|
|
74
79
|
// `auto_optimize_resources`.
|
|
75
80
|
|
|
76
81
|
var OPTIMIZE_ENABLED_RESOURCES = parseResourceList(process.env.SPICE_COUCHBASE_OPTIMIZE_RESOURCES);
|
|
77
|
-
var autoOptimizePolicy = null;
|
|
82
|
+
var autoOptimizePolicy = null; // One health monitor per process, shared by every copy of this module that
|
|
83
|
+
// ends up loaded. See connectionHealth.js.
|
|
84
|
+
|
|
85
|
+
var HEALTH_MONITOR_KEY = Symbol.for("spice-couchbase.health-monitor");
|
|
86
|
+
var health = globalThis[HEALTH_MONITOR_KEY] || (globalThis[HEALTH_MONITOR_KEY] = createHealthMonitor()); // Cluster handles live on the `spice.database` globals, which a reconnect
|
|
87
|
+
// replaces. Instances read them at call time instead of copying them.
|
|
88
|
+
|
|
89
|
+
var HANDLE_GLOBALS = {
|
|
90
|
+
cluster: "clusters",
|
|
91
|
+
connection: "connections",
|
|
92
|
+
scope: "scopes",
|
|
93
|
+
bucket_handle: "buckets"
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
function globalHandle(kind, connection_name) {
|
|
97
|
+
var _spice, _spice$database, _spice$database$HANDL;
|
|
98
|
+
|
|
99
|
+
if (typeof spice === "undefined") return undefined;
|
|
100
|
+
return (_spice = spice) === null || _spice === void 0 ? void 0 : (_spice$database = _spice.database) === null || _spice$database === void 0 ? void 0 : (_spice$database$HANDL = _spice$database[HANDLE_GLOBALS[kind]]) === null || _spice$database$HANDL === void 0 ? void 0 : _spice$database$HANDL[connection_name];
|
|
101
|
+
}
|
|
78
102
|
|
|
79
103
|
function requestedAutoOptimize(query_options) {
|
|
80
104
|
var _query_options$auto_o;
|
|
@@ -163,15 +187,12 @@ module.exports = class Database {
|
|
|
163
187
|
} = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
|
|
164
188
|
|
|
165
189
|
if (connection) {
|
|
166
|
-
var
|
|
190
|
+
var _spice2, _spice2$db;
|
|
167
191
|
|
|
168
192
|
this.connection_name = connection;
|
|
169
|
-
this.connection = spice.database.connections[connection];
|
|
170
193
|
this.query_scope = query_scope;
|
|
171
|
-
this.cluster = spice.database.clusters[connection];
|
|
172
194
|
this.bucket = this.connection._scope._bucket._name;
|
|
173
195
|
this.db_config = spice.config.database.connections[connection];
|
|
174
|
-
this.collection_manager = spice.database.buckets[connection].collections();
|
|
175
196
|
this.auto_create_collection = auto_create_collection || (collection || spice.config.database.connections[connection].collection) != undefined && (collection || spice.config.database.connections[connection].collection) != "default" && (scope || spice.config.database.connections[connection].scope) != "_default";
|
|
176
197
|
this.collection_name = collection || spice.config.database.connections[connection].collection || "_default";
|
|
177
198
|
/* console.log(
|
|
@@ -182,14 +203,11 @@ module.exports = class Database {
|
|
|
182
203
|
);*/
|
|
183
204
|
|
|
184
205
|
this.scope_name = scope || spice.config.database.connections[connection].scope || "_default";
|
|
185
|
-
this.scope = spice.database.scopes[connection];
|
|
186
206
|
this.full_text_index = spice.config.database.connections[connection].full_text_index || full_text_index;
|
|
187
207
|
this.N1qlQuery = couchbase.N1qlQuery;
|
|
188
208
|
this.SearchQuery = couchbase.SearchQuery;
|
|
189
|
-
this.collection = this.scope.collection(this.collection_name);
|
|
190
|
-
this.userManager = this.getUserManager(connection);
|
|
191
209
|
|
|
192
|
-
if (!((
|
|
210
|
+
if (!((_spice2 = spice) === null || _spice2 === void 0 ? void 0 : (_spice2$db = _spice2.db) === null || _spice2$db === void 0 ? void 0 : _spice2$db.collections)) {
|
|
193
211
|
spice.db = spice.db || {};
|
|
194
212
|
spice.db.collections = new Set();
|
|
195
213
|
}
|
|
@@ -204,6 +222,168 @@ module.exports = class Database {
|
|
|
204
222
|
this.auto_optimize = typeof configured_auto_optimize === "boolean" ? configured_auto_optimize : undefined;
|
|
205
223
|
this.enabled_resources = parseResourceList([...parseResourceList(this.db_config.auto_optimize_resources), ...parseResourceList(auto_optimize_resources)]);
|
|
206
224
|
}
|
|
225
|
+
} // Handles resolve from the globals on every access, so an instance created
|
|
226
|
+
// before a reconnect uses the new cluster afterwards. Assigning one pins it
|
|
227
|
+
// on that instance instead (tests, custom wiring).
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
pinHandle(kind, value) {
|
|
231
|
+
if (!Object.prototype.hasOwnProperty.call(this, "_pinned_handles")) {
|
|
232
|
+
Object.defineProperty(this, "_pinned_handles", {
|
|
233
|
+
value: {},
|
|
234
|
+
enumerable: false,
|
|
235
|
+
writable: true
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
this._pinned_handles[kind] = value;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
pinnedHandle(kind) {
|
|
243
|
+
var pinned = this._pinned_handles;
|
|
244
|
+
return pinned && kind in pinned ? {
|
|
245
|
+
value: pinned[kind]
|
|
246
|
+
} : null;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
handle(kind) {
|
|
250
|
+
var pinned = this.pinnedHandle(kind);
|
|
251
|
+
return pinned ? pinned.value : globalHandle(kind, this.connection_name);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
get cluster() {
|
|
255
|
+
return this.handle("cluster");
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
set cluster(value) {
|
|
259
|
+
this.pinHandle("cluster", value);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
get connection() {
|
|
263
|
+
return this.handle("connection");
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
set connection(value) {
|
|
267
|
+
this.pinHandle("connection", value);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
get scope() {
|
|
271
|
+
return this.handle("scope");
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
set scope(value) {
|
|
275
|
+
this.pinHandle("scope", value);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
get collection() {
|
|
279
|
+
var _this$_collection_cac;
|
|
280
|
+
|
|
281
|
+
var pinned = this.pinnedHandle("collection");
|
|
282
|
+
if (pinned) return pinned.value;
|
|
283
|
+
var scope = this.scope;
|
|
284
|
+
if (!scope) return undefined; // Collection handles are cheap but not free; cache one per scope object,
|
|
285
|
+
// which changes exactly when the connection is replaced.
|
|
286
|
+
|
|
287
|
+
if (((_this$_collection_cac = this._collection_cache) === null || _this$_collection_cac === void 0 ? void 0 : _this$_collection_cac.scope) !== scope) {
|
|
288
|
+
Object.defineProperty(this, "_collection_cache", {
|
|
289
|
+
value: {
|
|
290
|
+
scope,
|
|
291
|
+
collection: scope.collection(this.collection_name)
|
|
292
|
+
},
|
|
293
|
+
enumerable: false,
|
|
294
|
+
writable: true,
|
|
295
|
+
configurable: true
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
return this._collection_cache.collection;
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
set collection(value) {
|
|
303
|
+
this.pinHandle("collection", value);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
get collection_manager() {
|
|
307
|
+
var _this$handle;
|
|
308
|
+
|
|
309
|
+
var pinned = this.pinnedHandle("collection_manager");
|
|
310
|
+
if (pinned) return pinned.value;
|
|
311
|
+
return (_this$handle = this.handle("bucket_handle")) === null || _this$handle === void 0 ? void 0 : _this$handle.collections();
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
set collection_manager(value) {
|
|
315
|
+
this.pinHandle("collection_manager", value);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
get userManager() {
|
|
319
|
+
var _this$cluster;
|
|
320
|
+
|
|
321
|
+
var pinned = this.pinnedHandle("userManager");
|
|
322
|
+
if (pinned) return pinned.value;
|
|
323
|
+
return (_this$cluster = this.cluster) === null || _this$cluster === void 0 ? void 0 : _this$cluster.users();
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
set userManager(value) {
|
|
327
|
+
this.pinHandle("userManager", value);
|
|
328
|
+
}
|
|
329
|
+
/**
|
|
330
|
+
* Reports a finished operation to the health monitor. Only connectivity
|
|
331
|
+
* errors (timeouts, cancelled requests, service not available) count.
|
|
332
|
+
*/
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
recordOutcome(error) {
|
|
336
|
+
health.recordOutcome(this.connection_name, error);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
recordSettled(results) {
|
|
340
|
+
for (var result of results) {
|
|
341
|
+
if ((result === null || result === void 0 ? void 0 : result.status) === "rejected") this.recordOutcome(result.reason);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
observe(operation) {
|
|
346
|
+
var _this = this;
|
|
347
|
+
|
|
348
|
+
return _asyncToGenerator(function* () {
|
|
349
|
+
try {
|
|
350
|
+
return yield operation;
|
|
351
|
+
} catch (error) {
|
|
352
|
+
_this.recordOutcome(error);
|
|
353
|
+
|
|
354
|
+
throw error;
|
|
355
|
+
}
|
|
356
|
+
})();
|
|
357
|
+
}
|
|
358
|
+
/** Per-connection health: healthy, reconnecting, generation, last error. */
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
static healthStatus() {
|
|
362
|
+
return health.status();
|
|
363
|
+
}
|
|
364
|
+
/** Probes a connection now and reconnects it if the probe fails. */
|
|
365
|
+
|
|
366
|
+
|
|
367
|
+
static checkHealth() {
|
|
368
|
+
var connection_name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "default";
|
|
369
|
+
return health.check(connection_name);
|
|
370
|
+
}
|
|
371
|
+
/** Opens a fresh cluster for a connection and swaps it in. */
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
static forceReconnect() {
|
|
375
|
+
var connection_name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "default";
|
|
376
|
+
return health.reconnect(connection_name);
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* Stops probing and reconnecting and closes replaced clusters still in
|
|
380
|
+
* their grace period. Call before closing `spice.database.clusters` on
|
|
381
|
+
* shutdown so a reconnect cannot race it.
|
|
382
|
+
*/
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
static stopHealthMonitor() {
|
|
386
|
+
return health.stop();
|
|
207
387
|
}
|
|
208
388
|
/**
|
|
209
389
|
* Lets the application decide per resource whether slow statements may
|
|
@@ -309,25 +489,15 @@ module.exports = class Database {
|
|
|
309
489
|
}
|
|
310
490
|
|
|
311
491
|
connect(connection_name, config) {
|
|
312
|
-
var _this = this;
|
|
313
|
-
|
|
314
492
|
return _asyncToGenerator(function* () {
|
|
315
|
-
var
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
connectOptions.configProfile = config.configProfile;
|
|
322
|
-
}
|
|
493
|
+
var {
|
|
494
|
+
cluster,
|
|
495
|
+
bucket,
|
|
496
|
+
scope,
|
|
497
|
+
collection
|
|
498
|
+
} = yield openHandles(config); // Watch the connection from here on and replace it if it wedges.
|
|
323
499
|
|
|
324
|
-
|
|
325
|
-
var bucket = cluster.bucket(config.bucket);
|
|
326
|
-
var collection;
|
|
327
|
-
var scope_name = _this.scope_name || config.scope || "_default";
|
|
328
|
-
var collection_name = _this.collection_name || config.collection || "_default";
|
|
329
|
-
var scope = bucket.scope(scope_name);
|
|
330
|
-
collection = bucket.scope(scope_name).collection(collection_name || "_default");
|
|
500
|
+
health.register(connection_name, config);
|
|
331
501
|
return {
|
|
332
502
|
connection: connection_name,
|
|
333
503
|
bucket,
|
|
@@ -339,7 +509,7 @@ module.exports = class Database {
|
|
|
339
509
|
}
|
|
340
510
|
|
|
341
511
|
getUserManager(connection) {
|
|
342
|
-
return
|
|
512
|
+
return globalHandle("cluster", connection).users();
|
|
343
513
|
}
|
|
344
514
|
|
|
345
515
|
assignUserPermission() {
|
|
@@ -412,9 +582,9 @@ module.exports = class Database {
|
|
|
412
582
|
|
|
413
583
|
return _asyncToGenerator(function* () {
|
|
414
584
|
try {
|
|
415
|
-
var
|
|
585
|
+
var _spice3, _spice3$db, _spice3$db$collection;
|
|
416
586
|
|
|
417
|
-
if ((
|
|
587
|
+
if ((_spice3 = spice) === null || _spice3 === void 0 ? void 0 : (_spice3$db = _spice3.db) === null || _spice3$db === void 0 ? void 0 : (_spice3$db$collection = _spice3$db.collections) === null || _spice3$db$collection === void 0 ? void 0 : _spice3$db$collection.has(collection_name)) {
|
|
418
588
|
return true;
|
|
419
589
|
} else {
|
|
420
590
|
var response = yield _this4.cluster.query("SELECT RAW keyspace_id\n FROM system:keyspaces\n WHERE bucket_id = ?\n AND scope_id = ?\n AND keyspace_id = ?\n LIMIT 1", {
|
|
@@ -525,7 +695,7 @@ module.exports = class Database {
|
|
|
525
695
|
var collection = _this9.use_bucket_name ? _this9.connection : _this9.collection;
|
|
526
696
|
|
|
527
697
|
if (!useLookupIn) {
|
|
528
|
-
var response = yield collection.get(doc_id);
|
|
698
|
+
var response = yield _this9.observe(collection.get(doc_id));
|
|
529
699
|
return _objectSpread({
|
|
530
700
|
id: doc_id
|
|
531
701
|
}, response.content);
|
|
@@ -543,7 +713,10 @@ module.exports = class Database {
|
|
|
543
713
|
var specs = batch.map(col => couchbase.LookupInSpec.get(col));
|
|
544
714
|
return collection.lookupIn(doc_id, specs);
|
|
545
715
|
});
|
|
546
|
-
var batchResults = yield Promise.allSettled(batchPromises);
|
|
716
|
+
var batchResults = yield Promise.allSettled(batchPromises);
|
|
717
|
+
|
|
718
|
+
_this9.recordSettled(batchResults); // Merge results from all batches
|
|
719
|
+
|
|
547
720
|
|
|
548
721
|
batchResults.forEach((result, batchIndex) => {
|
|
549
722
|
if (result.status === "fulfilled" && result.value) {
|
|
@@ -567,9 +740,9 @@ module.exports = class Database {
|
|
|
567
740
|
|
|
568
741
|
return _asyncToGenerator(function* () {
|
|
569
742
|
try {
|
|
570
|
-
yield (_this10.use_bucket_name ? _this10.connection : _this10.collection).insert(doc_id, document, {
|
|
743
|
+
yield _this10.observe((_this10.use_bucket_name ? _this10.connection : _this10.collection).insert(doc_id, document, {
|
|
571
744
|
expiry: expiry
|
|
572
|
-
});
|
|
745
|
+
}));
|
|
573
746
|
} catch (e) {
|
|
574
747
|
console.log(e.message, e, doc_id);
|
|
575
748
|
}
|
|
@@ -583,7 +756,7 @@ module.exports = class Database {
|
|
|
583
756
|
|
|
584
757
|
var expiry = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
|
|
585
758
|
return _asyncToGenerator(function* () {
|
|
586
|
-
yield (_this11.use_bucket_name ? _this11.connection : _this11.collection).touch(doc_id, expiry);
|
|
759
|
+
yield _this11.observe((_this11.use_bucket_name ? _this11.connection : _this11.collection).touch(doc_id, expiry));
|
|
587
760
|
return yield _this11.get(doc_id);
|
|
588
761
|
})();
|
|
589
762
|
}
|
|
@@ -592,9 +765,9 @@ module.exports = class Database {
|
|
|
592
765
|
var _this12 = this;
|
|
593
766
|
|
|
594
767
|
return _asyncToGenerator(function* () {
|
|
595
|
-
var res = yield (_this12.use_bucket_name ? _this12.connection : _this12.collection).counter(doc_id, adjustment, {
|
|
768
|
+
var res = yield _this12.observe((_this12.use_bucket_name ? _this12.connection : _this12.collection).counter(doc_id, adjustment, {
|
|
596
769
|
initial: initial_value
|
|
597
|
-
});
|
|
770
|
+
}));
|
|
598
771
|
return res.value;
|
|
599
772
|
})();
|
|
600
773
|
}
|
|
@@ -603,9 +776,9 @@ module.exports = class Database {
|
|
|
603
776
|
var _this13 = this;
|
|
604
777
|
|
|
605
778
|
return _asyncToGenerator(function* () {
|
|
606
|
-
var res = yield (_this13.use_bucket_name ? _this13.connection : _this13.collection).replace(doc_id, document, {
|
|
779
|
+
var res = yield _this13.observe((_this13.use_bucket_name ? _this13.connection : _this13.collection).replace(doc_id, document, {
|
|
607
780
|
expiry: expiry
|
|
608
|
-
});
|
|
781
|
+
}));
|
|
609
782
|
return res.value;
|
|
610
783
|
})();
|
|
611
784
|
}
|
|
@@ -614,7 +787,7 @@ module.exports = class Database {
|
|
|
614
787
|
var _this14 = this;
|
|
615
788
|
|
|
616
789
|
return _asyncToGenerator(function* () {
|
|
617
|
-
var res = yield (_this14.use_bucket_name ? _this14.connection : _this14.collection).remove(doc_id);
|
|
790
|
+
var res = yield _this14.observe((_this14.use_bucket_name ? _this14.connection : _this14.collection).remove(doc_id));
|
|
618
791
|
return res.value;
|
|
619
792
|
})();
|
|
620
793
|
}
|
|
@@ -641,6 +814,8 @@ module.exports = class Database {
|
|
|
641
814
|
|
|
642
815
|
var returned_all = yield Promise.allSettled(functions);
|
|
643
816
|
|
|
817
|
+
_this15.recordSettled(returned_all);
|
|
818
|
+
|
|
644
819
|
var responses = _.flatten(_.map(returned_all, returned_obj => returned_obj === null || returned_obj === void 0 ? void 0 : returned_obj.value));
|
|
645
820
|
|
|
646
821
|
var _final_responses = _.compact(_.map(responses, (res, index) => {
|
|
@@ -679,7 +854,10 @@ module.exports = class Database {
|
|
|
679
854
|
var specs = batch.map(col => couchbase.LookupInSpec.get(col));
|
|
680
855
|
return collection.lookupIn(key, specs);
|
|
681
856
|
});
|
|
682
|
-
var batchResults = yield Promise.allSettled(batchPromises);
|
|
857
|
+
var batchResults = yield Promise.allSettled(batchPromises);
|
|
858
|
+
|
|
859
|
+
_this15.recordSettled(batchResults); // Merge results from all batches
|
|
860
|
+
|
|
683
861
|
|
|
684
862
|
batchResults.forEach((result, batchIndex) => {
|
|
685
863
|
if (result.status === "fulfilled" && result.value) {
|
|
@@ -1106,7 +1284,7 @@ module.exports = class Database {
|
|
|
1106
1284
|
|
|
1107
1285
|
var startTime = process.hrtime.bigint();
|
|
1108
1286
|
var options = buildQueryOptions(query_options);
|
|
1109
|
-
var response = yield exec_scope.query(sanitizedQuery, options); // End time
|
|
1287
|
+
var response = yield _this20.observe(exec_scope.query(sanitizedQuery, options)); // End time
|
|
1110
1288
|
|
|
1111
1289
|
var endTime = process.hrtime.bigint(); // Calculate duration in microseconds
|
|
1112
1290
|
|
package/package.json
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "spice-couchbase",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.63",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "build/index.js",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"test": "node src/utility.test.js",
|
|
8
8
|
"test:auto-optimize": "node --test src/index.auto-optimize.test.js",
|
|
9
9
|
"test:query-consistency": "node src/index.query-consistency.test.js",
|
|
10
|
-
"test:count-query": "node src/index.count-query.test.js"
|
|
10
|
+
"test:count-query": "node src/index.count-query.test.js",
|
|
11
|
+
"test:connection-health": "node --test src/connectionHealth.test.js"
|
|
11
12
|
},
|
|
12
13
|
"author": "Chad Fraser <chad@sonover.com> (http://sonover.com)",
|
|
13
14
|
"license": "ISC",
|