spice-couchbase 2.0.63 → 2.0.65

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/index.js CHANGED
@@ -1,39 +1,30 @@
1
1
  "use strict";
2
2
 
3
3
  var _process$env$SPICE_CO;
4
-
5
- function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; }
6
-
7
- function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(Object(source), true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(Object(source)).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; }
8
-
9
- function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
10
-
11
- function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { Promise.resolve(value).then(_next, _throw); } }
12
-
13
- function _asyncToGenerator(fn) { return function () { var self = this, args = arguments; return new Promise(function (resolve, reject) { var gen = fn.apply(self, args); function _next(value) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value); } function _throw(err) { asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err); } _next(undefined); }); }; }
14
-
4
+ function ownKeys(e, r) { var t = Object.keys(e); if (Object.getOwnPropertySymbols) { var o = Object.getOwnPropertySymbols(e); r && (o = o.filter(function (r) { return Object.getOwnPropertyDescriptor(e, r).enumerable; })), t.push.apply(t, o); } return t; }
5
+ function _objectSpread(e) { for (var r = 1; r < arguments.length; r++) { var t = null != arguments[r] ? arguments[r] : {}; r % 2 ? ownKeys(Object(t), !0).forEach(function (r) { _defineProperty(e, r, t[r]); }) : Object.getOwnPropertyDescriptors ? Object.defineProperties(e, Object.getOwnPropertyDescriptors(t)) : ownKeys(Object(t)).forEach(function (r) { Object.defineProperty(e, r, Object.getOwnPropertyDescriptor(t, r)); }); } return e; }
6
+ function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
7
+ function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
8
+ function _toPrimitive(t, e) { if ("object" != typeof t || !t) return t; var r; if ("undefined" != typeof Symbol && void 0 !== (r = t[Symbol.toPrimitive])) { var i = r.call(t, e || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === e ? String : Number)(t); }
9
+ function asyncGeneratorStep(n, t, e, r, o, a, c) { try { var i = n[a](c), u = i.value; } catch (n) { return void e(n); } i.done ? t(u) : Promise.resolve(u).then(r, o); }
10
+ function _asyncToGenerator(n) { return function () { var t = this, e = arguments; return new Promise(function (r, o) { var a = n.apply(t, e); function _next(n) { asyncGeneratorStep(a, r, o, _next, _throw, "next", n); } function _throw(n) { asyncGeneratorStep(a, r, o, _next, _throw, "throw", n); } _next(void 0); }); }; }
15
11
  var _ = require("lodash");
16
-
17
12
  var couchbase = require("couchbase");
18
-
19
- var {
20
- ConflictResolutionType
21
- } = require("couchbase");
22
-
23
- var {
24
- QueryScanConsistency
25
- } = require("couchbase");
26
-
27
- var {
28
- parseResourceList,
29
- extractStatementResources
30
- } = require("./utility");
31
-
32
- var {
33
- createHealthMonitor,
34
- openHandles
35
- } = require("./connectionHealth");
36
-
13
+ var _require = require("couchbase"),
14
+ ConflictResolutionType = _require.ConflictResolutionType;
15
+ var _require2 = require("couchbase"),
16
+ QueryScanConsistency = _require2.QueryScanConsistency;
17
+ var _require3 = require("./utility"),
18
+ parseResourceList = _require3.parseResourceList,
19
+ extractStatementResources = _require3.extractStatementResources,
20
+ statementShape = _require3.statementShape,
21
+ usesManagedIndex = _require3.usesManagedIndex,
22
+ renderIndexKey = _require3.renderIndexKey,
23
+ BASE_INDEX = _require3.BASE_INDEX,
24
+ MANAGED_INDEX_NAME = _require3.MANAGED_INDEX_NAME;
25
+ var _require4 = require("./connectionHealth"),
26
+ createHealthMonitor = _require4.createHealthMonitor,
27
+ openHandles = _require4.openHandles;
37
28
  if (!Promise.allSettled) {
38
29
  Promise.allSettled = promises => Promise.all(promises.map((promise, i) => promise.then(value => ({
39
30
  status: "fulfilled",
@@ -42,27 +33,29 @@ if (!Promise.allSettled) {
42
33
  status: "rejected",
43
34
  reason
44
35
  }))));
45
- } // Couchbase lookupIn has a limit of 16 operations per call
36
+ }
46
37
 
38
+ // Couchbase lookupIn has a limit of 16 operations per call
39
+ var LOOKUP_IN_MAX_SPECS = 16;
47
40
 
48
- var LOOKUP_IN_MAX_SPECS = 16; // Auto-optimisation guard rails. `query()` fires `optimize()` (an EXPLAIN,
41
+ // Auto-optimisation guard rails. `query()` fires `optimize()` (an EXPLAIN,
49
42
  // index inventory check, and possible CREATE INDEX) above SLOW_QUERY_MS. When
50
43
  // the cluster is degraded every statement is slow, so without limits each
51
44
  // query spawned two more heavy ones and deepened the outage. These caps make it
52
45
  // a bounded, once-per-statement background task that backs off on failure.
53
-
54
46
  var SLOW_QUERY_MS = Number(process.env.SPICE_COUCHBASE_SLOW_QUERY_MS || 3000);
55
47
  var AUTO_OPTIMIZE_ENABLED = !["0", "false", "no", "off"].includes(String((_process$env$SPICE_CO = process.env.SPICE_COUCHBASE_AUTO_OPTIMIZE) !== null && _process$env$SPICE_CO !== void 0 ? _process$env$SPICE_CO : "true").trim().toLowerCase());
56
48
  var OPTIMIZE_MAX_INFLIGHT = Number(process.env.SPICE_COUCHBASE_OPTIMIZE_MAX_INFLIGHT || 1);
57
- var OPTIMIZE_FAILURE_COOLDOWN_MS = Number(process.env.SPICE_COUCHBASE_OPTIMIZE_COOLDOWN_MS || 5 * 60 * 1000); // Statements already optimised in this process. Bounded so it cannot grow
49
+ var OPTIMIZE_FAILURE_COOLDOWN_MS = Number(process.env.SPICE_COUCHBASE_OPTIMIZE_COOLDOWN_MS || 5 * 60 * 1000);
50
+ // Statements already optimised in this process. Bounded so it cannot grow
58
51
  // without limit on a workload with many distinct literals.
59
-
60
52
  var OPTIMIZE_SEEN_LIMIT = 1000;
61
53
  var optimizeState = {
62
54
  inFlight: 0,
63
55
  seen: new Set(),
64
56
  cooldownUntil: 0
65
- }; // Auto cover indexing is opt-in per resource. A resource is the collection a
57
+ };
58
+ // Auto cover indexing is opt-in per resource. A resource is the collection a
66
59
  // Database instance serves (scoped deployments) or the `type = '...'` literal
67
60
  // legacy bucket mode adds to every statement; raw statements also count their
68
61
  // FROM / JOIN keyspaces. Names are case-insensitive. A slow statement is
@@ -77,118 +70,118 @@ var optimizeState = {
77
70
  // SPICE_COUCHBASE_OPTIMIZE_RESOURCES (`*` = every resource, which is the
78
71
  // pre-2.0.62 behaviour), or connection config / constructor option
79
72
  // `auto_optimize_resources`.
80
-
81
73
  var OPTIMIZE_ENABLED_RESOURCES = parseResourceList(process.env.SPICE_COUCHBASE_OPTIMIZE_RESOURCES);
82
- var autoOptimizePolicy = null; // One health monitor per process, shared by every copy of this module that
74
+ var autoOptimizePolicy = null;
75
+ // When the application installs a handler, slow statements are handed to it
76
+ // instead of creating indexes here: it owns every index decision, with its
77
+ // own key rules, caps and build windows. Without one, the legacy covering
78
+ // index path below still runs, so other applications keep their behaviour.
79
+ var slowStatementHandler = null;
80
+
81
+ // One health monitor per process, shared by every copy of this module that
83
82
  // ends up loaded. See connectionHealth.js.
84
-
85
83
  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.
84
+ var health = globalThis[HEALTH_MONITOR_KEY] || (globalThis[HEALTH_MONITOR_KEY] = createHealthMonitor());
88
85
 
86
+ // Cluster handles live on the `spice.database` globals, which a reconnect
87
+ // replaces. Instances read them at call time instead of copying them.
89
88
  var HANDLE_GLOBALS = {
90
89
  cluster: "clusters",
91
90
  connection: "connections",
92
91
  scope: "scopes",
93
92
  bucket_handle: "buckets"
94
93
  };
95
-
96
94
  function globalHandle(kind, connection_name) {
97
- var _spice, _spice$database, _spice$database$HANDL;
98
-
95
+ var _spice;
99
96
  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];
97
+ return (_spice = spice) === null || _spice === void 0 || (_spice = _spice.database) === null || _spice === void 0 || (_spice = _spice[HANDLE_GLOBALS[kind]]) === null || _spice === void 0 ? void 0 : _spice[connection_name];
101
98
  }
102
-
103
99
  function requestedAutoOptimize(query_options) {
104
100
  var _query_options$auto_o;
105
-
106
101
  if (!query_options || typeof query_options !== "object" || Array.isArray(query_options)) {
107
102
  return undefined;
108
103
  }
109
-
110
104
  var value = (_query_options$auto_o = query_options.auto_optimize) !== null && _query_options$auto_o !== void 0 ? _query_options$auto_o : query_options.optimize;
111
105
  return typeof value === "boolean" ? value : undefined;
112
106
  }
113
-
114
107
  function shouldOptimize(query) {
115
108
  if (!AUTO_OPTIMIZE_ENABLED) return false;
116
109
  if (optimizeState.inFlight >= OPTIMIZE_MAX_INFLIGHT) return false;
117
110
  if (Date.now() < optimizeState.cooldownUntil) return false;
118
111
  if (optimizeState.seen.has(query)) return false;
119
-
120
112
  if (optimizeState.seen.size >= OPTIMIZE_SEEN_LIMIT) {
121
113
  optimizeState.seen.delete(optimizeState.seen.values().next().value);
122
114
  }
123
-
124
115
  optimizeState.seen.add(query);
125
116
  return true;
126
117
  }
127
-
128
118
  function normalizeScanConsistency(value) {
129
119
  if (value === true || value === QueryScanConsistency.RequestPlus || typeof value === "string" && ["true", "request_plus"].includes(value.toLowerCase())) {
130
120
  return QueryScanConsistency.RequestPlus;
131
121
  }
132
-
133
122
  if (value === undefined || value === null || value === "" || value === false || value === QueryScanConsistency.NotBounded || typeof value === "string" && ["false", "not_bounded"].includes(value.toLowerCase())) {
134
123
  return QueryScanConsistency.NotBounded;
135
124
  }
136
-
137
125
  throw new Error("Unsupported query scan consistency: ".concat(value));
138
126
  }
139
-
140
127
  function buildQueryOptions() {
141
- var _queryOptions$consist, _queryOptions$scanWai;
142
-
128
+ var _queryOptions$consist, _queryOptions$scanWai, _queryOptions$clientC;
143
129
  var queryOptions = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};
144
-
145
130
  if (typeof queryOptions !== "object" || Array.isArray(queryOptions)) {
146
131
  queryOptions = {
147
132
  scan_consistency: queryOptions
148
133
  };
149
134
  }
150
-
151
135
  var options = {};
152
136
  var consistentWith = (_queryOptions$consist = queryOptions.consistentWith) !== null && _queryOptions$consist !== void 0 ? _queryOptions$consist : queryOptions.consistent_with;
153
-
154
137
  if (consistentWith !== undefined && consistentWith !== null) {
155
138
  options.consistentWith = consistentWith;
156
139
  } else {
157
140
  var _queryOptions$scanCon;
158
-
159
141
  options.scanConsistency = normalizeScanConsistency((_queryOptions$scanCon = queryOptions.scanConsistency) !== null && _queryOptions$scanCon !== void 0 ? _queryOptions$scanCon : queryOptions.scan_consistency);
160
142
  }
161
-
162
143
  var scanWait = (_queryOptions$scanWai = queryOptions.scanWait) !== null && _queryOptions$scanWai !== void 0 ? _queryOptions$scanWai : queryOptions.scan_wait;
163
-
164
144
  if (scanWait !== undefined && scanWait !== null && scanWait !== "") {
165
145
  var numericScanWait = Number(scanWait);
166
-
167
146
  if (!Number.isFinite(numericScanWait) || numericScanWait < 0) {
168
147
  throw new Error("Invalid query scan wait: ".concat(scanWait));
169
148
  }
170
-
171
149
  options.scanWait = numericScanWait;
172
150
  }
173
151
 
152
+ // Reported as clientContextID in system:completed_requests, which is how a
153
+ // slow statement is traced back to the route or job that ran it.
154
+ var clientContextId = (_queryOptions$clientC = queryOptions.clientContextId) !== null && _queryOptions$clientC !== void 0 ? _queryOptions$clientC : queryOptions.client_context_id;
155
+ if (clientContextId !== undefined && clientContextId !== null && clientContextId !== "") {
156
+ options.clientContextId = String(clientContextId).slice(0, 64);
157
+ }
174
158
  return options;
175
159
  }
160
+ function wantsScanWaitFallback(query_options) {
161
+ return query_options && typeof query_options === "object" && (query_options.scan_wait_fallback === true || query_options.scanWaitFallback === true);
162
+ }
176
163
 
164
+ // The query service reports an expired scan_wait as an index scan timeout
165
+ // (12015), which is a query error, not a connectivity timeout.
166
+ function isScanWaitExpired(error) {
167
+ var _error$cause$first_er, _error$cause, _error$context, _error$cause2, _error$context2;
168
+ var code = (_error$cause$first_er = error === null || error === void 0 || (_error$cause = error.cause) === null || _error$cause === void 0 ? void 0 : _error$cause.first_error_code) !== null && _error$cause$first_er !== void 0 ? _error$cause$first_er : error === null || error === void 0 || (_error$context = error.context) === null || _error$context === void 0 ? void 0 : _error$context.first_error_code;
169
+ var message = (error === null || error === void 0 || (_error$cause2 = error.cause) === null || _error$cause2 === void 0 ? void 0 : _error$cause2.first_error_message) || (error === null || error === void 0 || (_error$context2 = error.context) === null || _error$context2 === void 0 ? void 0 : _error$context2.first_error_message) || (error === null || error === void 0 ? void 0 : error.message) || "";
170
+ return code === 12015 || /index scan timed out|scan.{0,20}timed? ?out/i.test(message);
171
+ }
177
172
  module.exports = class Database {
178
173
  constructor(connection) {
179
- var {
180
- full_text_index,
181
- collection,
182
- scope,
183
- query_scope = "cluster",
184
- auto_create_collection,
185
- auto_optimize,
186
- auto_optimize_resources
187
- } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
188
-
174
+ var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
175
+ full_text_index = _ref.full_text_index,
176
+ collection = _ref.collection,
177
+ scope = _ref.scope,
178
+ _ref$query_scope = _ref.query_scope,
179
+ query_scope = _ref$query_scope === void 0 ? "cluster" : _ref$query_scope,
180
+ auto_create_collection = _ref.auto_create_collection,
181
+ auto_optimize = _ref.auto_optimize,
182
+ auto_optimize_resources = _ref.auto_optimize_resources;
189
183
  if (connection) {
190
- var _spice2, _spice2$db;
191
-
184
+ var _spice2;
192
185
  this.connection_name = connection;
193
186
  this.query_scope = query_scope;
194
187
  this.bucket = this.connection._scope._bucket._name;
@@ -201,32 +194,29 @@ module.exports = class Database {
201
194
  spice.config.database.connections[connection].collection,
202
195
  this.collection_name
203
196
  );*/
204
-
205
197
  this.scope_name = scope || spice.config.database.connections[connection].scope || "_default";
206
198
  this.full_text_index = spice.config.database.connections[connection].full_text_index || full_text_index;
207
199
  this.N1qlQuery = couchbase.N1qlQuery;
208
200
  this.SearchQuery = couchbase.SearchQuery;
209
-
210
- if (!((_spice2 = spice) === null || _spice2 === void 0 ? void 0 : (_spice2$db = _spice2.db) === null || _spice2$db === void 0 ? void 0 : _spice2$db.collections)) {
201
+ if (!((_spice2 = spice) !== null && _spice2 !== void 0 && (_spice2 = _spice2.db) !== null && _spice2 !== void 0 && _spice2.collections)) {
211
202
  spice.db = spice.db || {};
212
203
  spice.db.collections = new Set();
213
204
  }
214
-
215
205
  if (this.auto_create_collection) {
216
206
  this.upsertCollection();
217
207
  }
208
+ this.use_bucket_name = spice.config.database.connections[this.connection_name].scope == undefined;
218
209
 
219
- this.use_bucket_name = spice.config.database.connections[this.connection_name].scope == undefined; // Auto cover indexing. See OPTIMIZE_ENABLED_RESOURCES for precedence.
220
-
210
+ // Auto cover indexing. See OPTIMIZE_ENABLED_RESOURCES for precedence.
221
211
  var configured_auto_optimize = auto_optimize !== null && auto_optimize !== void 0 ? auto_optimize : this.db_config.auto_optimize;
222
212
  this.auto_optimize = typeof configured_auto_optimize === "boolean" ? configured_auto_optimize : undefined;
223
213
  this.enabled_resources = parseResourceList([...parseResourceList(this.db_config.auto_optimize_resources), ...parseResourceList(auto_optimize_resources)]);
224
214
  }
225
- } // Handles resolve from the globals on every access, so an instance created
215
+ }
216
+
217
+ // Handles resolve from the globals on every access, so an instance created
226
218
  // before a reconnect uses the new cluster afterwards. Assigning one pins it
227
219
  // on that instance instead (tests, custom wiring).
228
-
229
-
230
220
  pinHandle(kind, value) {
231
221
  if (!Object.prototype.hasOwnProperty.call(this, "_pinned_handles")) {
232
222
  Object.defineProperty(this, "_pinned_handles", {
@@ -235,55 +225,44 @@ module.exports = class Database {
235
225
  writable: true
236
226
  });
237
227
  }
238
-
239
228
  this._pinned_handles[kind] = value;
240
229
  }
241
-
242
230
  pinnedHandle(kind) {
243
231
  var pinned = this._pinned_handles;
244
232
  return pinned && kind in pinned ? {
245
233
  value: pinned[kind]
246
234
  } : null;
247
235
  }
248
-
249
236
  handle(kind) {
250
237
  var pinned = this.pinnedHandle(kind);
251
238
  return pinned ? pinned.value : globalHandle(kind, this.connection_name);
252
239
  }
253
-
254
240
  get cluster() {
255
241
  return this.handle("cluster");
256
242
  }
257
-
258
243
  set cluster(value) {
259
244
  this.pinHandle("cluster", value);
260
245
  }
261
-
262
246
  get connection() {
263
247
  return this.handle("connection");
264
248
  }
265
-
266
249
  set connection(value) {
267
250
  this.pinHandle("connection", value);
268
251
  }
269
-
270
252
  get scope() {
271
253
  return this.handle("scope");
272
254
  }
273
-
274
255
  set scope(value) {
275
256
  this.pinHandle("scope", value);
276
257
  }
277
-
278
258
  get collection() {
279
259
  var _this$_collection_cac;
280
-
281
260
  var pinned = this.pinnedHandle("collection");
282
261
  if (pinned) return pinned.value;
283
262
  var scope = this.scope;
284
- if (!scope) return undefined; // Collection handles are cheap but not free; cache one per scope object,
263
+ if (!scope) return undefined;
264
+ // Collection handles are cheap but not free; cache one per scope object,
285
265
  // which changes exactly when the connection is replaced.
286
-
287
266
  if (((_this$_collection_cac = this._collection_cache) === null || _this$_collection_cac === void 0 ? void 0 : _this$_collection_cac.scope) !== scope) {
288
267
  Object.defineProperty(this, "_collection_cache", {
289
268
  value: {
@@ -295,96 +274,80 @@ module.exports = class Database {
295
274
  configurable: true
296
275
  });
297
276
  }
298
-
299
277
  return this._collection_cache.collection;
300
278
  }
301
-
302
279
  set collection(value) {
303
280
  this.pinHandle("collection", value);
304
281
  }
305
-
306
282
  get collection_manager() {
307
283
  var _this$handle;
308
-
309
284
  var pinned = this.pinnedHandle("collection_manager");
310
285
  if (pinned) return pinned.value;
311
286
  return (_this$handle = this.handle("bucket_handle")) === null || _this$handle === void 0 ? void 0 : _this$handle.collections();
312
287
  }
313
-
314
288
  set collection_manager(value) {
315
289
  this.pinHandle("collection_manager", value);
316
290
  }
317
-
318
291
  get userManager() {
319
292
  var _this$cluster;
320
-
321
293
  var pinned = this.pinnedHandle("userManager");
322
294
  if (pinned) return pinned.value;
323
295
  return (_this$cluster = this.cluster) === null || _this$cluster === void 0 ? void 0 : _this$cluster.users();
324
296
  }
325
-
326
297
  set userManager(value) {
327
298
  this.pinHandle("userManager", value);
328
299
  }
300
+
329
301
  /**
330
302
  * Reports a finished operation to the health monitor. Only connectivity
331
303
  * errors (timeouts, cancelled requests, service not available) count.
332
304
  */
333
-
334
-
335
305
  recordOutcome(error) {
336
306
  health.recordOutcome(this.connection_name, error);
337
307
  }
338
-
339
308
  recordSettled(results) {
340
309
  for (var result of results) {
341
310
  if ((result === null || result === void 0 ? void 0 : result.status) === "rejected") this.recordOutcome(result.reason);
342
311
  }
343
312
  }
344
-
345
313
  observe(operation) {
346
314
  var _this = this;
347
-
348
315
  return _asyncToGenerator(function* () {
349
316
  try {
350
317
  return yield operation;
351
318
  } catch (error) {
352
319
  _this.recordOutcome(error);
353
-
354
320
  throw error;
355
321
  }
356
322
  })();
357
323
  }
358
- /** Per-connection health: healthy, reconnecting, generation, last error. */
359
-
360
324
 
325
+ /** Per-connection health: healthy, reconnecting, generation, last error. */
361
326
  static healthStatus() {
362
327
  return health.status();
363
328
  }
364
- /** Probes a connection now and reconnects it if the probe fails. */
365
-
366
329
 
330
+ /** Probes a connection now and reconnects it if the probe fails. */
367
331
  static checkHealth() {
368
332
  var connection_name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "default";
369
333
  return health.check(connection_name);
370
334
  }
371
- /** Opens a fresh cluster for a connection and swaps it in. */
372
-
373
335
 
336
+ /** Opens a fresh cluster for a connection and swaps it in. */
374
337
  static forceReconnect() {
375
338
  var connection_name = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : "default";
376
339
  return health.reconnect(connection_name);
377
340
  }
341
+
378
342
  /**
379
343
  * Stops probing and reconnecting and closes replaced clusters still in
380
344
  * their grace period. Call before closing `spice.database.clusters` on
381
345
  * shutdown so a reconnect cannot race it.
382
346
  */
383
-
384
-
385
347
  static stopHealthMonitor() {
386
348
  return health.stop();
387
349
  }
350
+
388
351
  /**
389
352
  * Lets the application decide per resource whether slow statements may
390
353
  * create covering indexes, e.g. from a per-resource setting an operator
@@ -392,53 +355,68 @@ module.exports = class Database {
392
355
  * returns true or false, or undefined to defer to the static lists. It is
393
356
  * synchronous because it runs on the query path. Pass null to clear.
394
357
  */
395
-
396
-
397
358
  static setAutoOptimizePolicy(policy) {
398
359
  if (policy !== null && policy !== undefined && typeof policy !== "function") {
399
360
  throw new TypeError("Auto-optimize policy must be a function or null");
400
361
  }
401
-
402
362
  autoOptimizePolicy = policy || null;
403
363
  }
404
-
405
364
  static getAutoOptimizePolicy() {
406
365
  return autoOptimizePolicy;
407
366
  }
367
+
408
368
  /**
409
- * Resources a statement touches, plus this instance's collection: that is
410
- * the keyspace `optimize()` would create the index on.
369
+ * Hands slow statements to the application instead of creating covering
370
+ * indexes here. `handler({ statement, shape, plan, resources, connection,
371
+ * bucket, scope, collection, duration_ms })` runs in the background, once
372
+ * per statement shape, after the resource opt-in policy has allowed it and
373
+ * only when the plan is not already served by a managed index. Pass null to
374
+ * restore the legacy behaviour.
411
375
  */
376
+ static setSlowStatementHandler(handler) {
377
+ if (handler !== null && handler !== undefined && typeof handler !== "function") {
378
+ throw new TypeError("Slow statement handler must be a function or null");
379
+ }
380
+ slowStatementHandler = handler || null;
381
+ }
382
+ static getSlowStatementHandler() {
383
+ return slowStatementHandler;
384
+ }
412
385
 
386
+ /** The fallback index every collection gets in place of a primary index. */
387
+ static get BASE_INDEX() {
388
+ return BASE_INDEX;
389
+ }
390
+ static get MANAGED_INDEX_NAME() {
391
+ return MANAGED_INDEX_NAME;
392
+ }
413
393
 
394
+ /**
395
+ * Resources a statement touches, plus this instance's collection: that is
396
+ * the keyspace `optimize()` would create the index on.
397
+ */
414
398
  statementResources(query) {
415
399
  var resources = extractStatementResources(query);
416
-
417
400
  if (this.collection_name && this.collection_name !== "_default") {
418
401
  resources.add(String(this.collection_name).toLowerCase());
419
402
  }
420
-
421
403
  return resources;
422
404
  }
405
+
423
406
  /**
424
407
  * True when a slow statement may create a covering index. See
425
408
  * OPTIMIZE_ENABLED_RESOURCES for the order of precedence.
426
409
  */
427
-
428
-
429
410
  shouldAutoOptimize(query) {
430
411
  var query_options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
431
412
  var requested = requestedAutoOptimize(query_options);
432
413
  if (requested !== undefined) return requested;
433
414
  if (typeof this.auto_optimize === "boolean") return this.auto_optimize;
434
415
  var resources = this.statementResources(query);
435
-
436
416
  if (typeof autoOptimizePolicy === "function" && resources.size > 0) {
437
417
  var deferred = false;
438
-
439
418
  for (var resource of resources) {
440
419
  var verdict = void 0;
441
-
442
420
  try {
443
421
  verdict = autoOptimizePolicy(resource, {
444
422
  query,
@@ -449,54 +427,42 @@ module.exports = class Database {
449
427
  // A broken policy must not create indexes nobody asked for.
450
428
  verdict = false;
451
429
  }
452
-
453
430
  if (verdict === true) return true;
454
431
  if (verdict === undefined || verdict === null) deferred = true;
455
432
  }
456
-
457
433
  if (!deferred) return false;
458
434
  }
459
-
460
435
  var enabled = new Set([...OPTIMIZE_ENABLED_RESOURCES, ...(this.enabled_resources || [])]);
461
436
  if (enabled.has("*")) return true;
462
-
463
437
  for (var _resource of resources) {
464
438
  if (enabled.has(_resource)) return true;
465
439
  }
466
-
467
440
  return false;
468
441
  }
469
-
470
442
  setGlobalConnectionObject(collection) {
471
443
  if (spice.checked_collections == undefined) {
472
444
  spice.checked_collections = {};
473
445
  }
474
-
475
446
  _.set(spice.checked_collections, collection, true);
476
447
  }
477
-
478
448
  checkGlobelConnectionObject(collection) {
479
449
  if (spice.checked_collections == undefined) {
480
450
  spice.checked_collections = {};
481
451
  }
482
-
483
452
  if (_.get(spice.checked_collections, collection)) {
484
453
  return true;
485
454
  }
486
-
487
455
  this.setGlobalConnectionObject(collection);
488
456
  return false;
489
457
  }
490
-
491
458
  connect(connection_name, config) {
492
459
  return _asyncToGenerator(function* () {
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.
499
-
460
+ var _yield$openHandles = yield openHandles(config),
461
+ cluster = _yield$openHandles.cluster,
462
+ bucket = _yield$openHandles.bucket,
463
+ scope = _yield$openHandles.scope,
464
+ collection = _yield$openHandles.collection;
465
+ // Watch the connection from here on and replace it if it wedges.
500
466
  health.register(connection_name, config);
501
467
  return {
502
468
  connection: connection_name,
@@ -507,14 +473,11 @@ module.exports = class Database {
507
473
  };
508
474
  })();
509
475
  }
510
-
511
476
  getUserManager(connection) {
512
477
  return globalHandle("cluster", connection).users();
513
478
  }
514
-
515
479
  assignUserPermission() {
516
480
  var _this2 = this;
517
-
518
481
  return _asyncToGenerator(function* () {
519
482
  try {
520
483
  var allUsers = yield _this2.userManager.getAllUsers({
@@ -533,42 +496,47 @@ module.exports = class Database {
533
496
  console.log(e);
534
497
  }
535
498
  })();
536
- } //create wait function
537
-
499
+ }
538
500
 
501
+ //create wait function
539
502
  wait(ms) {
540
503
  return _asyncToGenerator(function* () {
541
504
  return new Promise(resolve => setTimeout(resolve, ms));
542
505
  })();
543
506
  }
544
-
545
507
  quoteIdentifier(identifier) {
546
508
  return "`".concat(String(identifier).replace(/`/g, "``"), "`");
547
509
  }
548
-
549
510
  collectionPath(scope_name, collection_name) {
550
511
  return "default:".concat(this.quoteIdentifier(this.db_config.bucket), ".").concat(this.quoteIdentifier(scope_name), ".").concat(this.quoteIdentifier(collection_name));
551
512
  }
552
-
553
513
  isCollectionAlreadyExistsError(error) {
554
- var _error$cause, _error$context, _error$context2, _error$cause2, _error$cause3;
555
-
556
- var message = (error === null || error === void 0 ? void 0 : error.message) || (error === null || error === void 0 ? void 0 : (_error$cause = error.cause) === null || _error$cause === void 0 ? void 0 : _error$cause.first_error_message) || (error === null || error === void 0 ? void 0 : (_error$context = error.context) === null || _error$context === void 0 ? void 0 : _error$context.first_error_message) || (error === null || error === void 0 ? void 0 : (_error$context2 = error.context) === null || _error$context2 === void 0 ? void 0 : _error$context2.http_response_body) || "";
557
- return error instanceof couchbase.CollectionExistsError || (error === null || error === void 0 ? void 0 : (_error$cause2 = error.cause) === null || _error$cause2 === void 0 ? void 0 : _error$cause2.first_error_code) === 12003 || (error === null || error === void 0 ? void 0 : (_error$cause3 = error.cause) === null || _error$cause3 === void 0 ? void 0 : _error$cause3.first_error_code) === 12027 || /collection .*already exists|already exists/i.test(message);
514
+ var _error$cause3, _error$context3, _error$context4, _error$cause4, _error$cause5;
515
+ var message = (error === null || error === void 0 ? void 0 : error.message) || (error === null || error === void 0 || (_error$cause3 = error.cause) === null || _error$cause3 === void 0 ? void 0 : _error$cause3.first_error_message) || (error === null || error === void 0 || (_error$context3 = error.context) === null || _error$context3 === void 0 ? void 0 : _error$context3.first_error_message) || (error === null || error === void 0 || (_error$context4 = error.context) === null || _error$context4 === void 0 ? void 0 : _error$context4.http_response_body) || "";
516
+ return error instanceof couchbase.CollectionExistsError || (error === null || error === void 0 || (_error$cause4 = error.cause) === null || _error$cause4 === void 0 ? void 0 : _error$cause4.first_error_code) === 12003 || (error === null || error === void 0 || (_error$cause5 = error.cause) === null || _error$cause5 === void 0 ? void 0 : _error$cause5.first_error_code) === 12027 || /collection .*already exists|already exists/i.test(message);
558
517
  }
559
-
560
518
  upsertCollection() {
561
519
  var _this3 = this;
562
-
563
520
  return _asyncToGenerator(function* () {
564
521
  try {
565
522
  if (!_this3.checkGlobelConnectionObject(_this3.collection_name)) {
566
523
  var collection_exists = yield _this3.collectionExists(_this3.collection_name, _this3.scope_name);
567
-
568
524
  if (!collection_exists) {
525
+ var _this3$db_config;
569
526
  yield _this3.createCollection(_this3.collection_name, _this3.scope_name);
570
527
  yield _this3.wait(2000);
571
- yield _this3.createPrimaryIndex(_this3.scope_name, _this3.collection_name);
528
+ // `primary_index: false` on the connection replaces the primary
529
+ // index with the base secondary index. A new collection is empty,
530
+ // so building it straight away costs nothing.
531
+ if (((_this3$db_config = _this3.db_config) === null || _this3$db_config === void 0 ? void 0 : _this3$db_config.primary_index) === false) {
532
+ yield _this3.createIndex(_objectSpread(_objectSpread({}, BASE_INDEX), {}, {
533
+ defer_build: false,
534
+ collection: _this3.collection_name,
535
+ scope: _this3.scope_name
536
+ }));
537
+ } else {
538
+ yield _this3.createPrimaryIndex(_this3.scope_name, _this3.collection_name);
539
+ }
572
540
  }
573
541
  }
574
542
  } catch (e) {
@@ -576,27 +544,22 @@ module.exports = class Database {
576
544
  }
577
545
  })();
578
546
  }
579
-
580
547
  collectionExists(collection_name, scope_name) {
581
548
  var _this4 = this;
582
-
583
549
  return _asyncToGenerator(function* () {
584
550
  try {
585
- var _spice3, _spice3$db, _spice3$db$collection;
586
-
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)) {
551
+ var _spice3;
552
+ if ((_spice3 = spice) !== null && _spice3 !== void 0 && (_spice3 = _spice3.db) !== null && _spice3 !== void 0 && (_spice3 = _spice3.collections) !== null && _spice3 !== void 0 && _spice3.has(collection_name)) {
588
553
  return true;
589
554
  } else {
590
555
  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", {
591
556
  parameters: [_this4.db_config.bucket, scope_name, collection_name],
592
557
  scanConsistency: QueryScanConsistency.RequestPlus
593
558
  });
594
-
595
559
  if (Array.isArray(response === null || response === void 0 ? void 0 : response.rows) && response.rows.length > 0) {
596
560
  spice.db.collections.add(collection_name);
597
561
  return true;
598
562
  }
599
-
600
563
  return false;
601
564
  }
602
565
  } catch (e) {
@@ -605,10 +568,8 @@ module.exports = class Database {
605
568
  }
606
569
  })();
607
570
  }
608
-
609
571
  createCollection(connection_name, scope_name) {
610
572
  var _this5 = this;
611
-
612
573
  return _asyncToGenerator(function* () {
613
574
  try {
614
575
  var response = yield _this5.cluster.query("CREATE COLLECTION ".concat(_this5.collectionPath(scope_name, connection_name)), {
@@ -621,15 +582,12 @@ module.exports = class Database {
621
582
  spice.db.collections.add(connection_name);
622
583
  return;
623
584
  }
624
-
625
585
  console.log("CREATE Collection :::", e);
626
586
  }
627
587
  })();
628
588
  }
629
-
630
589
  primaryIndexExists(scope_name, col_name) {
631
590
  var _this6 = this;
632
-
633
591
  return _asyncToGenerator(function* () {
634
592
  try {
635
593
  var response = yield _this6.cluster.query("SELECT RAW name\n FROM system:indexes\n WHERE bucket_id = ?\n AND scope_id = ?\n AND keyspace_id = ?\n AND is_primary = true\n LIMIT 1", {
@@ -643,33 +601,26 @@ module.exports = class Database {
643
601
  }
644
602
  })();
645
603
  }
646
-
647
604
  createPrimaryIndex(scope_name, col_name) {
648
605
  var _this7 = this;
649
-
650
606
  return _asyncToGenerator(function* () {
651
607
  try {
652
608
  if (yield _this7.primaryIndexExists(scope_name, col_name)) {
653
609
  return;
654
610
  }
655
-
656
611
  var response = yield _this7.query("CREATE PRIMARY INDEX ON ".concat(_this7.collectionPath(scope_name, col_name)));
657
612
  return response;
658
613
  } catch (e) {
659
614
  var _e$cause, _e$cause2;
660
-
661
- if (e instanceof couchbase.IndexExistsError || (e === null || e === void 0 ? void 0 : (_e$cause = e.cause) === null || _e$cause === void 0 ? void 0 : _e$cause.first_error_code) === 4300 || /index .*already exists|index_exists/i.test((e === null || e === void 0 ? void 0 : e.message) || (e === null || e === void 0 ? void 0 : (_e$cause2 = e.cause) === null || _e$cause2 === void 0 ? void 0 : _e$cause2.first_error_message) || "")) {
615
+ if (e instanceof couchbase.IndexExistsError || (e === null || e === void 0 || (_e$cause = e.cause) === null || _e$cause === void 0 ? void 0 : _e$cause.first_error_code) === 4300 || /index .*already exists|index_exists/i.test((e === null || e === void 0 ? void 0 : e.message) || (e === null || e === void 0 || (_e$cause2 = e.cause) === null || _e$cause2 === void 0 ? void 0 : _e$cause2.first_error_message) || "")) {
662
616
  return;
663
617
  }
664
-
665
618
  console.log("CREATE Index :::", e);
666
619
  }
667
620
  })();
668
621
  }
669
-
670
622
  createScope(scope_name) {
671
623
  var _this8 = this;
672
-
673
624
  return _asyncToGenerator(function* () {
674
625
  try {
675
626
  yield _this8.collection_manager.createScope(scope_name);
@@ -683,41 +634,187 @@ module.exports = class Database {
683
634
  })();
684
635
  }
685
636
 
686
- get(doc_id) {
687
- var _this9 = this;
637
+ // Index management. Every statement here is DDL or a catalog read against a
638
+ // fully qualified keyspace, run at cluster level and outside `query()` so a
639
+ // slow build never feeds back into the slow-statement path.
640
+
641
+ runDdl(statement) {
642
+ var _arguments = arguments,
643
+ _this9 = this;
644
+ return _asyncToGenerator(function* () {
645
+ var options = _arguments.length > 1 && _arguments[1] !== undefined ? _arguments[1] : {};
646
+ return _this9.observe(_this9.cluster.query(statement, options));
647
+ })();
648
+ }
649
+
650
+ /**
651
+ * Every index on a collection, from system:indexes, including the legacy
652
+ * catalog form of the default collection. Rows carry name, index_key,
653
+ * condition, state, is_primary, using.
654
+ */
655
+ listIndexes() {
656
+ var _arguments2 = arguments,
657
+ _this0 = this;
658
+ return _asyncToGenerator(function* () {
659
+ var _response$meta;
660
+ var _ref2 = _arguments2.length > 0 && _arguments2[0] !== undefined ? _arguments2[0] : {},
661
+ _ref2$collection = _ref2.collection,
662
+ collection = _ref2$collection === void 0 ? _this0.collection_name : _ref2$collection,
663
+ _ref2$scope = _ref2.scope,
664
+ scope = _ref2$scope === void 0 ? _this0.scope_name : _ref2$scope;
665
+ var response = yield _this0.runDdl("SELECT RAW i FROM system:indexes AS i\n WHERE i.namespace_id = 'default'\n AND ((i.bucket_id = $bucket AND i.scope_id = $scope AND i.keyspace_id = $collection)\n OR ($scope = '_default' AND $collection = '_default'\n AND i.bucket_id IS MISSING AND i.scope_id IS MISSING AND i.keyspace_id = $bucket))", {
666
+ parameters: {
667
+ bucket: _this0.db_config.bucket,
668
+ scope,
669
+ collection
670
+ }
671
+ });
672
+ // A failed or partial inventory is not evidence that no index exists.
673
+ if (!Array.isArray(response === null || response === void 0 ? void 0 : response.rows) || response !== null && response !== void 0 && (_response$meta = response.meta) !== null && _response$meta !== void 0 && (_response$meta = _response$meta.warnings) !== null && _response$meta !== void 0 && _response$meta.length) {
674
+ throw new Error("Cannot read the index inventory for ".concat(scope, ".").concat(collection));
675
+ }
676
+ return response.rows;
677
+ })();
678
+ }
688
679
 
689
- var {
690
- columns = []
691
- } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
680
+ /**
681
+ * `CREATE INDEX IF NOT EXISTS`. Deferred by default so several indexes on
682
+ * one collection can be built together with `buildIndexes`, in one pass
683
+ * over the data.
684
+ */
685
+ createIndex() {
686
+ var _arguments3 = arguments,
687
+ _this1 = this;
688
+ return _asyncToGenerator(function* () {
689
+ var _ref3 = _arguments3.length > 0 && _arguments3[0] !== undefined ? _arguments3[0] : {},
690
+ name = _ref3.name,
691
+ keys = _ref3.keys,
692
+ where = _ref3.where,
693
+ _ref3$defer_build = _ref3.defer_build,
694
+ defer_build = _ref3$defer_build === void 0 ? true : _ref3$defer_build,
695
+ num_replica = _ref3.num_replica,
696
+ _ref3$collection = _ref3.collection,
697
+ collection = _ref3$collection === void 0 ? _this1.collection_name : _ref3$collection,
698
+ _ref3$scope = _ref3.scope,
699
+ scope = _ref3$scope === void 0 ? _this1.scope_name : _ref3$scope;
700
+ if (!name || !Array.isArray(keys) || keys.length === 0) {
701
+ throw new TypeError("createIndex needs a name and at least one key");
702
+ }
703
+ var withClause = {
704
+ defer_build: Boolean(defer_build)
705
+ };
706
+ if (Number.isInteger(num_replica) && num_replica >= 0) {
707
+ withClause.num_replica = num_replica;
708
+ }
709
+ var statement = "CREATE INDEX IF NOT EXISTS ".concat(_this1.quoteIdentifier(name), " ") + "ON ".concat(_this1.collectionPath(scope, collection), "(").concat(keys.map(renderIndexKey).join(", "), ")") + (where ? " WHERE ".concat(where) : "") + " WITH ".concat(JSON.stringify(withClause));
710
+ yield _this1.runDdl(statement);
711
+ return statement;
712
+ })();
713
+ }
714
+
715
+ /** Builds deferred indexes on one collection together. */
716
+ buildIndexes(names) {
717
+ var _arguments4 = arguments,
718
+ _this10 = this;
719
+ return _asyncToGenerator(function* () {
720
+ var _ref4 = _arguments4.length > 1 && _arguments4[1] !== undefined ? _arguments4[1] : {},
721
+ _ref4$collection = _ref4.collection,
722
+ collection = _ref4$collection === void 0 ? _this10.collection_name : _ref4$collection,
723
+ _ref4$scope = _ref4.scope,
724
+ scope = _ref4$scope === void 0 ? _this10.scope_name : _ref4$scope;
725
+ var list = [...new Set(names || [])];
726
+ if (list.length === 0) return null;
727
+ var statement = "BUILD INDEX ON ".concat(_this10.collectionPath(scope, collection)) + "(".concat(list.map(name => _this10.quoteIdentifier(name)).join(", "), ")");
728
+ yield _this10.runDdl(statement);
729
+ return statement;
730
+ })();
731
+ }
732
+ dropIndex(name) {
733
+ var _arguments5 = arguments,
734
+ _this11 = this;
735
+ return _asyncToGenerator(function* () {
736
+ var _ref5 = _arguments5.length > 1 && _arguments5[1] !== undefined ? _arguments5[1] : {},
737
+ _ref5$collection = _ref5.collection,
738
+ collection = _ref5$collection === void 0 ? _this11.collection_name : _ref5$collection,
739
+ _ref5$scope = _ref5.scope,
740
+ scope = _ref5$scope === void 0 ? _this11.scope_name : _ref5$scope;
741
+ var statement = "DROP INDEX IF EXISTS ".concat(_this11.quoteIdentifier(name), " ON ").concat(_this11.collectionPath(scope, collection));
742
+ yield _this11.runDdl(statement);
743
+ return statement;
744
+ })();
745
+ }
746
+ dropPrimaryIndex() {
747
+ var _arguments6 = arguments,
748
+ _this12 = this;
749
+ return _asyncToGenerator(function* () {
750
+ var _ref6 = _arguments6.length > 0 && _arguments6[0] !== undefined ? _arguments6[0] : {},
751
+ _ref6$collection = _ref6.collection,
752
+ collection = _ref6$collection === void 0 ? _this12.collection_name : _ref6$collection,
753
+ _ref6$scope = _ref6.scope,
754
+ scope = _ref6$scope === void 0 ? _this12.scope_name : _ref6$scope;
755
+ var statement = "DROP PRIMARY INDEX IF EXISTS ON ".concat(_this12.collectionPath(scope, collection));
756
+ yield _this12.runDdl(statement);
757
+ return statement;
758
+ })();
759
+ }
760
+
761
+ /** The plan for a statement, resolved in this instance's scope by default. */
762
+ explain(statement) {
763
+ var _arguments7 = arguments,
764
+ _this13 = this;
765
+ return _asyncToGenerator(function* () {
766
+ var _response$rows;
767
+ var query_scope = _arguments7.length > 1 && _arguments7[1] !== undefined ? _arguments7[1] : _this13.use_bucket_name ? "cluster" : "scope";
768
+ var exec_scope = query_scope === "cluster" ? _this13.cluster : _this13.scope;
769
+ var response = yield _this13.observe(exec_scope.query("EXPLAIN ".concat(statement)));
770
+ return response === null || response === void 0 || (_response$rows = response.rows) === null || _response$rows === void 0 ? void 0 : _response$rows[0];
771
+ })();
772
+ }
773
+
774
+ /** Couchbase's own index advice for a statement (the ADVISE statement). */
775
+ advise(statement) {
776
+ var _arguments8 = arguments,
777
+ _this14 = this;
692
778
  return _asyncToGenerator(function* () {
779
+ var _response$rows2;
780
+ var query_scope = _arguments8.length > 1 && _arguments8[1] !== undefined ? _arguments8[1] : _this14.use_bucket_name ? "cluster" : "scope";
781
+ var exec_scope = query_scope === "cluster" ? _this14.cluster : _this14.scope;
782
+ var response = yield _this14.observe(exec_scope.query("ADVISE ".concat(statement)));
783
+ return response === null || response === void 0 || (_response$rows2 = response.rows) === null || _response$rows2 === void 0 ? void 0 : _response$rows2[0];
784
+ })();
785
+ }
786
+ get(doc_id) {
787
+ var _arguments9 = arguments,
788
+ _this15 = this;
789
+ return _asyncToGenerator(function* () {
790
+ var _ref7 = _arguments9.length > 1 && _arguments9[1] !== undefined ? _arguments9[1] : {},
791
+ _ref7$columns = _ref7.columns,
792
+ columns = _ref7$columns === void 0 ? [] : _ref7$columns;
693
793
  try {
694
794
  var useLookupIn = columns.length > 0;
695
- var collection = _this9.use_bucket_name ? _this9.connection : _this9.collection;
696
-
795
+ var collection = _this15.use_bucket_name ? _this15.connection : _this15.collection;
697
796
  if (!useLookupIn) {
698
- var response = yield _this9.observe(collection.get(doc_id));
797
+ var response = yield _this15.observe(collection.get(doc_id));
699
798
  return _objectSpread({
700
799
  id: doc_id
701
800
  }, response.content);
702
- } // Batch columns into chunks of LOOKUP_IN_MAX_SPECS (16)
703
-
801
+ }
704
802
 
803
+ // Batch columns into chunks of LOOKUP_IN_MAX_SPECS (16)
705
804
  var value = {
706
805
  id: doc_id
707
806
  };
807
+ var columnBatches = _.chunk(columns, LOOKUP_IN_MAX_SPECS);
708
808
 
709
- var columnBatches = _.chunk(columns, LOOKUP_IN_MAX_SPECS); // Execute all batch requests in parallel
710
-
711
-
809
+ // Execute all batch requests in parallel
712
810
  var batchPromises = columnBatches.map(batch => {
713
811
  var specs = batch.map(col => couchbase.LookupInSpec.get(col));
714
812
  return collection.lookupIn(doc_id, specs);
715
813
  });
716
814
  var batchResults = yield Promise.allSettled(batchPromises);
815
+ _this15.recordSettled(batchResults);
717
816
 
718
- _this9.recordSettled(batchResults); // Merge results from all batches
719
-
720
-
817
+ // Merge results from all batches
721
818
  batchResults.forEach((result, batchIndex) => {
722
819
  if (result.status === "fulfilled" && result.value) {
723
820
  var batch = columnBatches[batchIndex];
@@ -734,131 +831,109 @@ module.exports = class Database {
734
831
  }
735
832
  })();
736
833
  }
737
-
738
834
  insert(doc_id, document, expiry) {
739
- var _this10 = this;
740
-
835
+ var _this16 = this;
741
836
  return _asyncToGenerator(function* () {
742
837
  try {
743
- yield _this10.observe((_this10.use_bucket_name ? _this10.connection : _this10.collection).insert(doc_id, document, {
838
+ yield _this16.observe((_this16.use_bucket_name ? _this16.connection : _this16.collection).insert(doc_id, document, {
744
839
  expiry: expiry
745
840
  }));
746
841
  } catch (e) {
747
842
  console.log(e.message, e, doc_id);
748
843
  }
749
-
750
- return yield _this10.get(doc_id);
844
+ return yield _this16.get(doc_id);
751
845
  })();
752
846
  }
753
-
754
847
  touch(doc_id) {
755
- var _this11 = this;
756
-
757
- var expiry = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : 0;
848
+ var _arguments0 = arguments,
849
+ _this17 = this;
758
850
  return _asyncToGenerator(function* () {
759
- yield _this11.observe((_this11.use_bucket_name ? _this11.connection : _this11.collection).touch(doc_id, expiry));
760
- return yield _this11.get(doc_id);
851
+ var expiry = _arguments0.length > 1 && _arguments0[1] !== undefined ? _arguments0[1] : 0;
852
+ yield _this17.observe((_this17.use_bucket_name ? _this17.connection : _this17.collection).touch(doc_id, expiry));
853
+ return yield _this17.get(doc_id);
761
854
  })();
762
855
  }
763
-
764
856
  count(doc_id, adjustment, initial_value) {
765
- var _this12 = this;
766
-
857
+ var _this18 = this;
767
858
  return _asyncToGenerator(function* () {
768
- var res = yield _this12.observe((_this12.use_bucket_name ? _this12.connection : _this12.collection).counter(doc_id, adjustment, {
859
+ var res = yield _this18.observe((_this18.use_bucket_name ? _this18.connection : _this18.collection).counter(doc_id, adjustment, {
769
860
  initial: initial_value
770
861
  }));
771
862
  return res.value;
772
863
  })();
773
864
  }
774
-
775
865
  update(doc_id, document, expiry) {
776
- var _this13 = this;
777
-
866
+ var _this19 = this;
778
867
  return _asyncToGenerator(function* () {
779
- var res = yield _this13.observe((_this13.use_bucket_name ? _this13.connection : _this13.collection).replace(doc_id, document, {
868
+ var res = yield _this19.observe((_this19.use_bucket_name ? _this19.connection : _this19.collection).replace(doc_id, document, {
780
869
  expiry: expiry
781
870
  }));
782
871
  return res.value;
783
872
  })();
784
873
  }
785
-
786
874
  delete(doc_id) {
787
- var _this14 = this;
788
-
875
+ var _this20 = this;
789
876
  return _asyncToGenerator(function* () {
790
- var res = yield _this14.observe((_this14.use_bucket_name ? _this14.connection : _this14.collection).remove(doc_id));
877
+ var res = yield _this20.observe((_this20.use_bucket_name ? _this20.connection : _this20.collection).remove(doc_id));
791
878
  return res.value;
792
879
  })();
793
880
  }
794
-
795
881
  multi_get(keys) {
796
- var _this15 = this;
797
-
798
- var {
799
- keep_type = true,
800
- columns = []
801
- } = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {};
882
+ var _arguments1 = arguments,
883
+ _this21 = this;
802
884
  return _asyncToGenerator(function* () {
885
+ var _ref8 = _arguments1.length > 1 && _arguments1[1] !== undefined ? _arguments1[1] : {},
886
+ _ref8$keep_type = _ref8.keep_type,
887
+ keep_type = _ref8$keep_type === void 0 ? true : _ref8$keep_type,
888
+ _ref8$columns = _ref8.columns,
889
+ columns = _ref8$columns === void 0 ? [] : _ref8$columns;
803
890
  if (keys.length == 0) {
804
891
  return [];
805
892
  }
806
-
807
893
  if (_.isArray(keys) && keys.length > 0) {
808
894
  var useLookupIn = columns.length > 0;
809
- var collection = _this15.use_bucket_name ? _this15.connection : _this15.collection;
810
-
895
+ var collection = _this21.use_bucket_name ? _this21.connection : _this21.collection;
811
896
  if (!useLookupIn) {
812
897
  // Regular get - no batching needed
813
898
  var functions = _.map(keys, key => collection.get(key));
814
-
815
899
  var returned_all = yield Promise.allSettled(functions);
816
-
817
- _this15.recordSettled(returned_all);
818
-
900
+ _this21.recordSettled(returned_all);
819
901
  var responses = _.flatten(_.map(returned_all, returned_obj => returned_obj === null || returned_obj === void 0 ? void 0 : returned_obj.value));
820
-
821
902
  var _final_responses = _.compact(_.map(responses, (res, index) => {
822
903
  if (res) {
823
904
  var value = res.value;
824
-
825
905
  if (!keep_type && value) {
826
906
  delete value.type;
827
907
  }
828
-
829
908
  if (value) {
830
909
  value.id = keys[index];
831
910
  }
832
-
833
911
  return value;
834
912
  }
835
913
  }));
836
-
837
914
  return _final_responses;
838
- } // lookupIn with batching for columns > 16
839
-
840
-
841
- var columnBatches = _.chunk(columns, LOOKUP_IN_MAX_SPECS); // For each key, we need to fetch all column batches and merge the results
915
+ }
842
916
 
917
+ // lookupIn with batching for columns > 16
918
+ var columnBatches = _.chunk(columns, LOOKUP_IN_MAX_SPECS);
843
919
 
844
- var keyPromises = _.map(keys,
845
- /*#__PURE__*/
846
- function () {
847
- var _ref = _asyncToGenerator(function* (key) {
920
+ // For each key, we need to fetch all column batches and merge the results
921
+ var keyPromises = _.map(keys, /*#__PURE__*/function () {
922
+ var _ref9 = _asyncToGenerator(function* (key) {
848
923
  try {
849
924
  var value = {
850
925
  id: key
851
- }; // Execute all batch requests for this key in parallel
926
+ };
852
927
 
928
+ // Execute all batch requests for this key in parallel
853
929
  var batchPromises = columnBatches.map(batch => {
854
930
  var specs = batch.map(col => couchbase.LookupInSpec.get(col));
855
931
  return collection.lookupIn(key, specs);
856
932
  });
857
933
  var batchResults = yield Promise.allSettled(batchPromises);
934
+ _this21.recordSettled(batchResults);
858
935
 
859
- _this15.recordSettled(batchResults); // Merge results from all batches
860
-
861
-
936
+ // Merge results from all batches
862
937
  batchResults.forEach((result, batchIndex) => {
863
938
  if (result.status === "fulfilled" && result.value) {
864
939
  var batch = columnBatches[batchIndex];
@@ -869,75 +944,56 @@ module.exports = class Database {
869
944
  });
870
945
  }
871
946
  });
872
-
873
947
  if (!keep_type && value) {
874
948
  delete value.type;
875
949
  }
876
-
877
950
  return value;
878
951
  } catch (e) {
879
952
  return null;
880
953
  }
881
954
  });
882
-
883
955
  return function (_x) {
884
- return _ref.apply(this, arguments);
956
+ return _ref9.apply(this, arguments);
885
957
  };
886
958
  }());
887
-
888
959
  var results = yield Promise.allSettled(keyPromises);
889
-
890
960
  var final_responses = _.compact(_.map(results, result => {
891
961
  if (result.status === "fulfilled" && result.value) {
892
962
  // Only return if we got at least some data beyond just the id
893
963
  return Object.keys(result.value).length > 1 ? result.value : null;
894
964
  }
895
-
896
965
  return null;
897
966
  }));
898
-
899
967
  return final_responses;
900
968
  } else {
901
969
  return [];
902
970
  }
903
971
  })();
904
972
  }
905
-
906
973
  get_view(view, design_doc, search_query, limit, offset) {
907
974
  var that = this;
908
- return new Promise(
909
- /*#__PURE__*/
910
- function () {
911
- var _ref2 = _asyncToGenerator(function* (resolve, reject) {
975
+ return new Promise(/*#__PURE__*/function () {
976
+ var _ref0 = _asyncToGenerator(function* (resolve, reject) {
912
977
  var ViewQuery = couchbase.ViewQuery;
913
978
  var query = ViewQuery.from(design_doc, view);
914
-
915
979
  if (!limit) {
916
980
  limit = 0;
917
981
  }
918
-
919
982
  if (!offset) {
920
983
  offset = 0;
921
984
  }
922
-
923
985
  if (limit > 0) {
924
986
  query.limit(limit);
925
987
  }
926
-
927
988
  query.skip(offset);
928
-
929
989
  if (search_query) {
930
990
  if (search_query.length > 0) {
931
991
  query.range(search_query, search_query);
932
992
  }
933
993
  }
934
-
935
- that.connection.query(query,
936
- /*#__PURE__*/
937
- function () {
938
- var _ref3 = _asyncToGenerator(function* (err, results, matrix) {
994
+ that.connection.query(query, /*#__PURE__*/function () {
995
+ var _ref1 = _asyncToGenerator(function* (err, results, matrix) {
939
996
  var return_value = [];
940
-
941
997
  if (err) {
942
998
  reject(err);
943
999
  } else {
@@ -948,7 +1004,6 @@ module.exports = class Database {
948
1004
  return_value.push(results[i].key);
949
1005
  }
950
1006
  }
951
-
952
1007
  try {
953
1008
  var created = yield that.multi_get(return_value);
954
1009
  resolve(created);
@@ -957,42 +1012,38 @@ module.exports = class Database {
957
1012
  }
958
1013
  }
959
1014
  });
960
-
961
1015
  return function (_x4, _x5, _x6) {
962
- return _ref3.apply(this, arguments);
1016
+ return _ref1.apply(this, arguments);
963
1017
  };
964
1018
  }());
965
1019
  });
966
-
967
1020
  return function (_x2, _x3) {
968
- return _ref2.apply(this, arguments);
1021
+ return _ref0.apply(this, arguments);
969
1022
  };
970
1023
  }());
971
1024
  }
972
-
973
1025
  search(type) {
974
- var _this16 = this;
975
-
976
- var columns = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "";
977
- var query = arguments.length > 2 ? arguments[2] : undefined;
978
- var limit = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
979
- var offset = arguments.length > 4 && arguments[4] !== undefined ? arguments[4] : 0;
980
- var order_by = arguments.length > 5 ? arguments[5] : undefined;
981
- var do_count = arguments.length > 6 && arguments[6] !== undefined ? arguments[6] : "false";
982
- var query_scope = arguments.length > 7 ? arguments[7] : undefined;
983
- var join = arguments.length > 8 && arguments[8] !== undefined ? arguments[8] : "";
984
- var query_options = arguments.length > 9 && arguments[9] !== undefined ? arguments[9] : {};
1026
+ var _arguments10 = arguments,
1027
+ _this22 = this;
985
1028
  return _asyncToGenerator(function* () {
986
- var is_legacy = _this16.scope_name !== "_default" && _this16.collection_name !== "_default";
987
- var bucket = "`".concat(_this16.use_bucket_name ? _this16.bucket : _this16.collection_name, "`");
988
-
1029
+ var columns = _arguments10.length > 1 && _arguments10[1] !== undefined ? _arguments10[1] : "";
1030
+ var query = _arguments10.length > 2 ? _arguments10[2] : undefined;
1031
+ var limit = _arguments10.length > 3 && _arguments10[3] !== undefined ? _arguments10[3] : 0;
1032
+ var offset = _arguments10.length > 4 && _arguments10[4] !== undefined ? _arguments10[4] : 0;
1033
+ var order_by = _arguments10.length > 5 ? _arguments10[5] : undefined;
1034
+ var do_count = _arguments10.length > 6 && _arguments10[6] !== undefined ? _arguments10[6] : "false";
1035
+ var query_scope = _arguments10.length > 7 ? _arguments10[7] : undefined;
1036
+ var join = _arguments10.length > 8 && _arguments10[8] !== undefined ? _arguments10[8] : "";
1037
+ var query_options = _arguments10.length > 9 && _arguments10[9] !== undefined ? _arguments10[9] : {};
1038
+ var is_legacy = _this22.scope_name !== "_default" && _this22.collection_name !== "_default";
1039
+ var bucket = "`".concat(_this22.use_bucket_name ? _this22.bucket : _this22.collection_name, "`");
989
1040
  function processColumns(columnsStr) {
990
1041
  // If columns is empty or *, return default
991
1042
  if (columnsStr === "*" || !columnsStr) {
992
1043
  return "(".concat(bucket, ").*, META(").concat(bucket, ").id AS id");
993
- } // Split by comma and process each column
994
-
1044
+ }
995
1045
 
1046
+ // Split by comma and process each column
996
1047
  var columnList = columnsStr.split(",").map(col => col.trim());
997
1048
  var hasId = false;
998
1049
  var processedColumns = columnList.map(col => {
@@ -1000,10 +1051,9 @@ module.exports = class Database {
1000
1051
  // Patterns: `collection`.`id`, collection.id, `id`, id
1001
1052
  var idPattern = /^(?:`?(\w+)`?\.)?`?id`?$/i;
1002
1053
  var match = col.match(idPattern);
1003
-
1004
1054
  if (match) {
1005
- hasId = true; // match[1] contains the collection name if present
1006
-
1055
+ hasId = true;
1056
+ // match[1] contains the collection name if present
1007
1057
  if (match[1]) {
1008
1058
  // Has collection prefix: `collection`.`id` or collection.id
1009
1059
  return "META(`".concat(match[1], "`).id AS id");
@@ -1011,52 +1061,47 @@ module.exports = class Database {
1011
1061
  // Just id without collection prefix
1012
1062
  return "META(".concat(bucket, ").id AS id");
1013
1063
  }
1014
- } // Return the column as-is
1015
-
1064
+ }
1016
1065
 
1066
+ // Return the column as-is
1017
1067
  return col;
1018
- }); // If no id column was provided, add it automatically
1068
+ });
1019
1069
 
1070
+ // If no id column was provided, add it automatically
1020
1071
  if (!hasId) {
1021
1072
  processedColumns.push("META(".concat(bucket, ").id AS id"));
1022
1073
  }
1023
-
1024
1074
  return processedColumns.join(", ");
1025
1075
  }
1026
-
1027
1076
  try {
1028
1077
  columns = processColumns(columns);
1029
1078
  var front_query = "SELECT ".concat(columns, " FROM ").concat(bucket, " ").concat(join);
1030
1079
  var conditions = [];
1031
-
1032
- if (_this16.use_bucket_name) {
1080
+ if (_this22.use_bucket_name) {
1033
1081
  conditions.push("type = '".concat(type, "'"));
1034
1082
  }
1035
-
1036
1083
  if (query) {
1037
1084
  conditions.push(query);
1038
- } // The conditions are folded into `where_clause` itself so that both queries below are built
1085
+ }
1086
+
1087
+ // The conditions are folded into `where_clause` itself so that both queries below are built
1039
1088
  // from one string. Previously this held the bare literal " WHERE " and only the data query
1040
1089
  // appended `conditions.join(" AND ")`, which left the count query emitting a dangling
1041
1090
  // `WHERE` — Couchbase answered "syntax error - at end of input" and every `do_count=true`
1042
1091
  // request failed with a 400, on every resource.
1043
-
1044
-
1045
1092
  var where_clause = conditions.length ? " WHERE ".concat(conditions.join(" AND ")) : "";
1046
1093
  query = front_query + where_clause;
1047
- var count_query = "SELECT count(meta(".concat(bucket, ").id) as count FROM ").concat(bucket, " ").concat(join, " ").concat(where_clause); //console.log("Cound Query::", count_query);
1048
-
1094
+ var count_query = "SELECT count(meta(".concat(bucket, ").id) as count FROM ").concat(bucket, " ").concat(join, " ").concat(where_clause);
1095
+ //console.log("Cound Query::", count_query);
1049
1096
  var order_by_clause = order_by ? " ORDER BY ".concat(order_by) : "";
1050
1097
  var limit_clause = limit > 0 ? " LIMIT ".concat(limit) : "";
1051
1098
  var offset_clause = offset > 0 ? " OFFSET ".concat(offset) : "";
1052
1099
  query = query + order_by_clause + limit_clause + offset_clause;
1053
- var active_query_space = query_scope || (!_this16.use_bucket_name ? "scope" : "cluster");
1054
- var queries = [_this16.getData(query, active_query_space, query_options)];
1055
-
1100
+ var active_query_space = query_scope || (!_this22.use_bucket_name ? "scope" : "cluster");
1101
+ var queries = [_this22.getData(query, active_query_space, query_options)];
1056
1102
  if (do_count != "false") {
1057
- queries.push(_this16.getCount(count_query, active_query_space, query_options));
1103
+ queries.push(_this22.getCount(count_query, active_query_space, query_options));
1058
1104
  }
1059
-
1060
1105
  var responses = yield Promise.all(queries);
1061
1106
  return {
1062
1107
  data: responses[0],
@@ -1067,38 +1112,28 @@ module.exports = class Database {
1067
1112
  }
1068
1113
  })();
1069
1114
  }
1070
-
1071
1115
  full_text_search(type, search_query, limit) {
1072
1116
  var offset = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : 0;
1073
1117
  var full_text_index = arguments.length > 4 ? arguments[4] : undefined;
1074
1118
  var that = this;
1075
- return new Promise(
1076
- /*#__PURE__*/
1077
- function () {
1078
- var _ref4 = _asyncToGenerator(function* (resolve, reject) {
1119
+ return new Promise(/*#__PURE__*/function () {
1120
+ var _ref10 = _asyncToGenerator(function* (resolve, reject) {
1079
1121
  try {
1080
1122
  var full_query = search_query + " AND type:" + type;
1081
1123
  var query = that.SearchQuery.new(that.full_text_index || full_text_index, that.SearchQuery.queryString(full_query));
1082
-
1083
1124
  if (!limit) {
1084
1125
  limit = 0;
1085
1126
  }
1086
-
1087
1127
  if (!offset) {
1088
1128
  offset = 0;
1089
1129
  }
1090
-
1091
1130
  if (limit > 0) {
1092
1131
  query.limit(limit);
1093
1132
  }
1094
-
1095
1133
  query.skip(offset);
1096
- that.cluster.query(query,
1097
- /*#__PURE__*/
1098
- function () {
1099
- var _ref5 = _asyncToGenerator(function* (err, results, matrix) {
1134
+ that.cluster.query(query, /*#__PURE__*/function () {
1135
+ var _ref11 = _asyncToGenerator(function* (err, results, matrix) {
1100
1136
  var return_value = [];
1101
-
1102
1137
  if (err) {
1103
1138
  reject(err);
1104
1139
  } else {
@@ -1109,7 +1144,6 @@ module.exports = class Database {
1109
1144
  return_value.push(results[i].id);
1110
1145
  }
1111
1146
  }
1112
-
1113
1147
  try {
1114
1148
  var getted = yield that.multi_get(return_value);
1115
1149
  resolve({
@@ -1121,64 +1155,58 @@ module.exports = class Database {
1121
1155
  }
1122
1156
  }
1123
1157
  });
1124
-
1125
- return function (_x9, _x10, _x11) {
1126
- return _ref5.apply(this, arguments);
1158
+ return function (_x9, _x0, _x1) {
1159
+ return _ref11.apply(this, arguments);
1127
1160
  };
1128
1161
  }());
1129
1162
  } catch (e) {
1130
1163
  reject(e);
1131
1164
  }
1132
1165
  });
1133
-
1134
1166
  return function (_x7, _x8) {
1135
- return _ref4.apply(this, arguments);
1167
+ return _ref10.apply(this, arguments);
1136
1168
  };
1137
1169
  }());
1138
1170
  }
1139
-
1140
1171
  getCount(count_query_element) {
1141
- var _this17 = this;
1142
-
1143
- var query_scope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "cluster";
1144
- var query_options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1172
+ var _arguments11 = arguments,
1173
+ _this23 = this;
1145
1174
  return _asyncToGenerator(function* () {
1146
- var response = yield _this17.query(count_query_element, query_scope, query_options);
1175
+ var query_scope = _arguments11.length > 1 && _arguments11[1] !== undefined ? _arguments11[1] : "cluster";
1176
+ var query_options = _arguments11.length > 2 && _arguments11[2] !== undefined ? _arguments11[2] : {};
1177
+ var response = yield _this23.query(count_query_element, query_scope, query_options);
1147
1178
  return response.rows[0].count;
1148
1179
  })();
1149
1180
  }
1150
-
1151
1181
  getData(query_element) {
1152
- var _this18 = this;
1153
-
1154
- var query_scope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : "cluster";
1155
- var query_options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1182
+ var _arguments12 = arguments,
1183
+ _this24 = this;
1156
1184
  return _asyncToGenerator(function* () {
1157
- var response = yield _this18.query(query_element, query_scope, query_options);
1185
+ var query_scope = _arguments12.length > 1 && _arguments12[1] !== undefined ? _arguments12[1] : "cluster";
1186
+ var query_options = _arguments12.length > 2 && _arguments12[2] !== undefined ? _arguments12[2] : {};
1187
+ var response = yield _this24.query(query_element, query_scope, query_options);
1158
1188
  return response.rows;
1159
1189
  })();
1160
1190
  }
1191
+
1161
1192
  /**
1162
1193
  * Recursively checks if the query plan contains a Fetch operator
1163
1194
  * @param {Object} plan - The EXPLAIN query plan
1164
1195
  * @returns {boolean} - True if Fetch operator is found
1165
1196
  */
1166
-
1167
-
1168
1197
  hasFetchOperator(plan) {
1169
1198
  if (!plan || typeof plan !== "object") {
1170
1199
  return false;
1171
- } // Check if this node is a Fetch operator
1172
-
1200
+ }
1173
1201
 
1202
+ // Check if this node is a Fetch operator
1174
1203
  if (plan["#operator"] === "Fetch") {
1175
1204
  return true;
1176
- } // Recursively check all child properties
1177
-
1205
+ }
1178
1206
 
1207
+ // Recursively check all child properties
1179
1208
  for (var key of Object.keys(plan)) {
1180
1209
  var value = plan[key];
1181
-
1182
1210
  if (Array.isArray(value)) {
1183
1211
  for (var item of value) {
1184
1212
  if (this.hasFetchOperator(item)) {
@@ -1191,63 +1219,57 @@ module.exports = class Database {
1191
1219
  }
1192
1220
  }
1193
1221
  }
1194
-
1195
1222
  return false;
1196
1223
  }
1197
-
1198
1224
  optimize(query, exec_scope) {
1199
- var _this19 = this;
1200
-
1225
+ var _this25 = this;
1201
1226
  return _asyncToGenerator(function* () {
1202
1227
  optimizeState.inFlight += 1;
1203
-
1204
1228
  try {
1205
- var _explainResponse$rows, _indexResponse$meta, _indexResponse$meta$w;
1206
-
1207
- var {
1208
- buildCoveringIndexDefinition,
1209
- indexSatisfiesCoveringDefinition
1210
- } = require("./utility"); // 1. Run EXPLAIN to get the query plan
1211
-
1229
+ var _explainResponse$rows, _indexResponse$meta;
1230
+ var _require5 = require("./utility"),
1231
+ buildCoveringIndexDefinition = _require5.buildCoveringIndexDefinition,
1232
+ indexSatisfiesCoveringDefinition = _require5.indexSatisfiesCoveringDefinition;
1212
1233
 
1234
+ // 1. Run EXPLAIN to get the query plan
1213
1235
  var explainQuery = "EXPLAIN ".concat(query);
1214
- var explainResponse = yield exec_scope.query(explainQuery); // 2. Check if query plan contains Fetch operator (not covered)
1215
-
1216
- var plan = explainResponse === null || explainResponse === void 0 ? void 0 : (_explainResponse$rows = explainResponse.rows) === null || _explainResponse$rows === void 0 ? void 0 : _explainResponse$rows[0];
1236
+ var explainResponse = yield exec_scope.query(explainQuery);
1217
1237
 
1218
- if (!_this19.hasFetchOperator(plan)) {
1238
+ // 2. Check if query plan contains Fetch operator (not covered)
1239
+ var plan = explainResponse === null || explainResponse === void 0 || (_explainResponse$rows = explainResponse.rows) === null || _explainResponse$rows === void 0 ? void 0 : _explainResponse$rows[0];
1240
+ if (!_this25.hasFetchOperator(plan)) {
1219
1241
  // Query is already covered, no need to create index
1220
1242
  return;
1221
- } // 3. Build covering index from the query
1222
-
1243
+ }
1223
1244
 
1245
+ // 3. Build covering index from the query
1224
1246
  var candidate = buildCoveringIndexDefinition(query, {
1225
- bucket: _this19.bucket,
1226
- scope: _this19.scope_name,
1227
- collection: _this19.collection_name
1247
+ bucket: _this25.bucket,
1248
+ scope: _this25.scope_name,
1249
+ collection: _this25.collection_name
1228
1250
  });
1229
- if (!candidate) return; // EXPLAIN only describes the chosen plan. An equivalent or wider index
1251
+ if (!candidate) return;
1252
+
1253
+ // EXPLAIN only describes the chosen plan. An equivalent or wider index
1230
1254
  // may already exist under an older name, or still be deferred/building.
1231
1255
  // Include both catalog representations of the default collection.
1232
-
1233
1256
  var indexResponse = yield exec_scope.query("SELECT RAW i FROM system:indexes AS i\n WHERE i.namespace_id = 'default'\n AND ((i.bucket_id = $bucket AND i.scope_id = $scope AND i.keyspace_id = $collection)\n OR ($scope = '_default' AND $collection = '_default'\n AND i.bucket_id IS MISSING AND i.scope_id IS MISSING AND i.keyspace_id = $bucket))", {
1234
1257
  parameters: {
1235
1258
  bucket: candidate.bucket,
1236
1259
  scope: candidate.scope,
1237
1260
  collection: candidate.collection
1238
1261
  }
1239
- }); // A failed or incomplete inventory is not evidence that no index exists.
1240
-
1241
- if (!Array.isArray(indexResponse === null || indexResponse === void 0 ? void 0 : indexResponse.rows) || (indexResponse === null || indexResponse === void 0 ? void 0 : (_indexResponse$meta = indexResponse.meta) === null || _indexResponse$meta === void 0 ? void 0 : (_indexResponse$meta$w = _indexResponse$meta.warnings) === null || _indexResponse$meta$w === void 0 ? void 0 : _indexResponse$meta$w.length)) {
1262
+ });
1263
+ // A failed or incomplete inventory is not evidence that no index exists.
1264
+ if (!Array.isArray(indexResponse === null || indexResponse === void 0 ? void 0 : indexResponse.rows) || indexResponse !== null && indexResponse !== void 0 && (_indexResponse$meta = indexResponse.meta) !== null && _indexResponse$meta !== void 0 && (_indexResponse$meta = _indexResponse$meta.warnings) !== null && _indexResponse$meta !== void 0 && _indexResponse$meta.length) {
1242
1265
  throw new Error("Cannot verify existing indexes before auto-optimization");
1243
1266
  }
1244
-
1245
1267
  if (indexResponse.rows.some(index => index.name === candidate.name || indexSatisfiesCoveringDefinition(index, candidate))) {
1246
1268
  return;
1247
- } // Identical definitions use the same name across processes. Couchbase's
1248
- // per-keyspace name uniqueness also prevents a concurrent duplicate.
1249
-
1269
+ }
1250
1270
 
1271
+ // Identical definitions use the same name across processes. Couchbase's
1272
+ // per-keyspace name uniqueness also prevents a concurrent duplicate.
1251
1273
  yield exec_scope.query(candidate.statement);
1252
1274
  } catch (e) {
1253
1275
  // A failure here (timeout, index already exists, cluster busy) almost
@@ -1259,44 +1281,90 @@ module.exports = class Database {
1259
1281
  }
1260
1282
  })();
1261
1283
  }
1262
-
1263
1284
  query(query, query_scope) {
1264
- var _this20 = this;
1265
-
1266
- var query_options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};
1285
+ var _arguments13 = arguments,
1286
+ _this26 = this;
1267
1287
  return _asyncToGenerator(function* () {
1288
+ var query_options = _arguments13.length > 2 && _arguments13[2] !== undefined ? _arguments13[2] : {};
1268
1289
  if (query_scope && typeof query_scope === "object" && !Array.isArray(query_scope)) {
1269
1290
  query_options = query_scope;
1270
1291
  query_scope = undefined;
1271
1292
  }
1272
-
1273
- var scope = query_scope || _this20.query_scope || "cluster";
1293
+ var scope = query_scope || _this26.query_scope || "cluster";
1274
1294
  var sanitizedQuery = query; //sanitization step to be implemented
1275
-
1276
1295
  var exec_scope = null;
1277
-
1278
1296
  if (scope === "cluster") {
1279
- exec_scope = _this20.cluster;
1297
+ exec_scope = _this26.cluster;
1280
1298
  } else {
1281
- exec_scope = _this20.scope;
1282
- } // Start time
1283
-
1284
-
1299
+ exec_scope = _this26.scope;
1300
+ }
1301
+ // Start time
1285
1302
  var startTime = process.hrtime.bigint();
1286
1303
  var options = buildQueryOptions(query_options);
1287
- var response = yield _this20.observe(exec_scope.query(sanitizedQuery, options)); // End time
1288
-
1289
- var endTime = process.hrtime.bigint(); // Calculate duration in microseconds
1290
-
1291
- var durationInMilliseconds = (endTime - startTime) / BigInt(1000000); // Optimise slow statements in the background, subject to the guard rails
1304
+ var response;
1305
+ try {
1306
+ response = yield _this26.observe(exec_scope.query(sanitizedQuery, options));
1307
+ } catch (error) {
1308
+ // `scan_wait_fallback`: a request_plus read that could not get a fresh
1309
+ // index snapshot within `scan_wait` runs once more without waiting, for
1310
+ // callers that prefer a slightly stale answer to an error.
1311
+ if (!wantsScanWaitFallback(query_options) || options.scanConsistency !== QueryScanConsistency.RequestPlus || options.scanWait === undefined || !isScanWaitExpired(error)) {
1312
+ throw error;
1313
+ }
1314
+ console.warn("[spice-couchbase] index not current within ".concat(options.scanWait, "ms; reading without waiting"));
1315
+ response = yield _this26.observe(exec_scope.query(sanitizedQuery, _objectSpread(_objectSpread({}, _.omit(options, ["scanWait"])), {}, {
1316
+ scanConsistency: QueryScanConsistency.NotBounded
1317
+ })));
1318
+ }
1319
+ // End time
1320
+ var endTime = process.hrtime.bigint();
1321
+ // Calculate duration in microseconds
1322
+ var durationInMilliseconds = (endTime - startTime) / BigInt(1000000);
1323
+ // Optimise slow statements in the background, subject to the guard rails
1292
1324
  // declared at the top of this file. Never awaited, never allowed to reject.
1293
-
1294
- if (durationInMilliseconds > SLOW_QUERY_MS && _this20.shouldAutoOptimize(sanitizedQuery, query_options) && shouldOptimize(sanitizedQuery)) {
1295
- _this20.optimize(sanitizedQuery, exec_scope).catch(() => {});
1325
+ if (durationInMilliseconds > SLOW_QUERY_MS && _this26.shouldAutoOptimize(sanitizedQuery, query_options)) {
1326
+ if (slowStatementHandler) {
1327
+ if (shouldOptimize(statementShape(sanitizedQuery))) {
1328
+ _this26.reportSlowStatement(sanitizedQuery, exec_scope, Number(durationInMilliseconds)).catch(() => {});
1329
+ }
1330
+ } else if (shouldOptimize(sanitizedQuery)) {
1331
+ _this26.optimize(sanitizedQuery, exec_scope).catch(() => {});
1332
+ }
1296
1333
  }
1297
-
1298
1334
  return response;
1299
1335
  })();
1300
1336
  }
1301
1337
 
1338
+ /**
1339
+ * Hands a slow statement to the application's handler, unless its plan is
1340
+ * already served by a managed index. Runs under the same in-flight and
1341
+ * cooldown guard rails as `optimize()`.
1342
+ */
1343
+ reportSlowStatement(query, exec_scope, duration_ms) {
1344
+ var _this27 = this;
1345
+ return _asyncToGenerator(function* () {
1346
+ optimizeState.inFlight += 1;
1347
+ try {
1348
+ var _explainResponse$rows2;
1349
+ var explainResponse = yield exec_scope.query("EXPLAIN ".concat(query));
1350
+ var plan = explainResponse === null || explainResponse === void 0 || (_explainResponse$rows2 = explainResponse.rows) === null || _explainResponse$rows2 === void 0 ? void 0 : _explainResponse$rows2[0];
1351
+ if (usesManagedIndex(plan)) return;
1352
+ yield slowStatementHandler({
1353
+ statement: query,
1354
+ shape: statementShape(query),
1355
+ plan,
1356
+ resources: [..._this27.statementResources(query)],
1357
+ connection: _this27.connection_name,
1358
+ bucket: _this27.bucket,
1359
+ scope: _this27.scope_name,
1360
+ collection: _this27.collection_name,
1361
+ duration_ms
1362
+ });
1363
+ } catch (e) {
1364
+ optimizeState.cooldownUntil = Date.now() + OPTIMIZE_FAILURE_COOLDOWN_MS;
1365
+ } finally {
1366
+ optimizeState.inFlight -= 1;
1367
+ }
1368
+ })();
1369
+ }
1302
1370
  };