couchbase 4.7.0 → 4.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/deps/couchbase-cxx-cache/mozilla-ca-bundle.crt +3 -575
  2. package/deps/couchbase-cxx-cache/mozilla-ca-bundle.sha256 +1 -1
  3. package/deps/couchbase-cxx-client/CMakeLists.txt +3 -1
  4. package/deps/couchbase-cxx-client/README.md +2 -2
  5. package/deps/couchbase-cxx-client/core/error_context/base_error_context.hxx +32 -0
  6. package/deps/couchbase-cxx-client/core/error_context/key_value.cxx +1 -0
  7. package/deps/couchbase-cxx-client/core/error_context/key_value.hxx +23 -0
  8. package/deps/couchbase-cxx-client/core/error_context/key_value_error_context.hxx +35 -0
  9. package/deps/couchbase-cxx-client/core/error_context/subdocument_error_context.hxx +41 -0
  10. package/deps/couchbase-cxx-client/core/impl/binary_collection.cxx +123 -88
  11. package/deps/couchbase-cxx-client/core/impl/collection.cxx +416 -189
  12. package/deps/couchbase-cxx-client/core/impl/error.cxx +29 -4
  13. package/deps/couchbase-cxx-client/core/impl/invoke_with_node_id.hxx +44 -0
  14. package/deps/couchbase-cxx-client/core/impl/node_id.cxx +110 -0
  15. package/deps/couchbase-cxx-client/core/impl/node_id.hxx +40 -0
  16. package/deps/couchbase-cxx-client/core/io/configuration_belongs_to_session.hxx +67 -0
  17. package/deps/couchbase-cxx-client/core/io/http_session_manager.hxx +97 -57
  18. package/deps/couchbase-cxx-client/core/io/mcbp_session.cxx +7 -16
  19. package/deps/couchbase-cxx-client/core/operations/document_get_all_replicas.hxx +14 -4
  20. package/deps/couchbase-cxx-client/core/operations/document_get_projected.cxx +3 -4
  21. package/deps/couchbase-cxx-client/core/operations/document_lookup_in_all_replicas.hxx +4 -0
  22. package/deps/couchbase-cxx-client/core/operations/document_query.cxx +12 -12
  23. package/deps/couchbase-cxx-client/core/operations/management/collection_create.cxx +11 -11
  24. package/deps/couchbase-cxx-client/core/operations/management/collection_drop.cxx +11 -11
  25. package/deps/couchbase-cxx-client/core/operations/management/collection_update.cxx +11 -11
  26. package/deps/couchbase-cxx-client/core/operations/management/error_utils.cxx +9 -6
  27. package/deps/couchbase-cxx-client/core/operations/management/query_index_create.cxx +5 -4
  28. package/deps/couchbase-cxx-client/core/operations/management/query_index_drop.cxx +7 -6
  29. package/deps/couchbase-cxx-client/core/operations/management/scope_create.cxx +12 -13
  30. package/deps/couchbase-cxx-client/core/operations/management/scope_drop.cxx +8 -9
  31. package/deps/couchbase-cxx-client/core/topology/configuration.cxx +21 -0
  32. package/deps/couchbase-cxx-client/core/topology/configuration.hxx +28 -0
  33. package/deps/couchbase-cxx-client/core/utils/contains_string.cxx +61 -0
  34. package/deps/couchbase-cxx-client/core/utils/contains_string.hxx +26 -0
  35. package/deps/couchbase-cxx-client/couchbase/collection.hxx +73 -0
  36. package/deps/couchbase-cxx-client/couchbase/error.hxx +16 -0
  37. package/deps/couchbase-cxx-client/couchbase/node_id.hxx +123 -0
  38. package/deps/couchbase-cxx-client/couchbase/node_id_for_options.hxx +61 -0
  39. package/deps/couchbase-cxx-client/couchbase/node_ids_options.hxx +62 -0
  40. package/deps/couchbase-cxx-client/couchbase/result.hxx +42 -0
  41. package/dist/binding.d.ts +1 -0
  42. package/dist/bucket.d.ts +9 -1
  43. package/dist/bucket.js +21 -0
  44. package/dist/cluster.d.ts +10 -1
  45. package/dist/cluster.js +22 -0
  46. package/dist/collection.d.ts +3 -3
  47. package/dist/collection.js +161 -123
  48. package/dist/diagnosticsexecutor.js +2 -2
  49. package/dist/diagnosticstypes.d.ts +36 -0
  50. package/dist/diagnosticstypes.js +22 -1
  51. package/dist/observability.d.ts +5 -1
  52. package/dist/observability.js +11 -3
  53. package/dist/observabilityhandler.d.ts +6 -0
  54. package/dist/observabilityhandler.js +51 -14
  55. package/dist/observabilitytypes.js +19 -42
  56. package/dist/observabilityutilities.js +1 -1
  57. package/dist/oteltracer.d.ts +5 -0
  58. package/dist/oteltracer.js +7 -0
  59. package/dist/queryindexmanager.js +15 -0
  60. package/dist/thresholdlogging.d.ts +5 -0
  61. package/dist/thresholdlogging.js +7 -0
  62. package/dist/tracing.d.ts +6 -0
  63. package/dist/version.d.ts +1 -1
  64. package/dist/version.js +1 -1
  65. package/dist/waituntilreadyexecutor.d.ts +37 -0
  66. package/dist/waituntilreadyexecutor.js +156 -0
  67. package/package.json +8 -8
  68. package/scripts/prebuilds.js +13 -0
  69. package/src/binding.cpp +1 -1
@@ -21,6 +21,7 @@
21
21
  #include "get_all_replicas.hxx"
22
22
  #include "get_any_replica.hxx"
23
23
  #include "internal_scan_result.hxx"
24
+ #include "invoke_with_node_id.hxx"
24
25
  #include "observability_recorder.hxx"
25
26
  #include "observe_poll.hxx"
26
27
 
@@ -96,6 +97,10 @@
96
97
 
97
98
  #include <spdlog/fmt/bundled/core.h>
98
99
 
100
+ #include <asio/error.hpp>
101
+ #include <asio/steady_timer.hpp>
102
+
103
+ #include <atomic>
99
104
  #include <chrono>
100
105
  #include <cstdint>
101
106
  #include <future>
@@ -111,6 +116,8 @@
111
116
 
112
117
  namespace couchbase
113
118
  {
119
+ using core::impl::invoke_with_node_id;
120
+
114
121
  class collection_impl : public std::enable_shared_from_this<collection_impl>
115
122
  {
116
123
  public:
@@ -171,17 +178,18 @@ public:
171
178
  { options.retry_strategy },
172
179
  obs_rec->operation_span(),
173
180
  };
174
- return core_.execute(
175
- std::move(request),
176
- [obs_rec = std::move(obs_rec),
177
- crypto_manager = crypto_manager_,
178
- handler = std::move(handler)](auto resp) mutable {
179
- obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
180
- return handler(
181
- core::impl::make_error(std::move(resp.ctx)),
182
- get_result{
183
- resp.cas, { std::move(resp.value), resp.flags }, {}, std::move(crypto_manager) });
184
- });
181
+ return core_.execute(std::move(request),
182
+ [obs_rec = std::move(obs_rec),
183
+ crypto_manager = crypto_manager_,
184
+ handler = std::move(handler)](auto resp) mutable {
185
+ obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
186
+ invoke_with_node_id(std::move(handler),
187
+ core::impl::make_error(std::move(resp.ctx)),
188
+ get_result{ resp.cas,
189
+ { std::move(resp.value), resp.flags },
190
+ {},
191
+ std::move(crypto_manager) });
192
+ });
185
193
  }
186
194
  core::operations::get_projected_request request{
187
195
  core::document_id{
@@ -200,22 +208,22 @@ public:
200
208
  { options.retry_strategy },
201
209
  obs_rec->operation_span(),
202
210
  };
203
- return core_.execute(
204
- std::move(request),
205
- [obs_rec = std::move(obs_rec),
206
- crypto_manager = crypto_manager_,
207
- handler = std::move(handler)](auto resp) mutable {
208
- std::optional<std::chrono::system_clock::time_point> expiry_time{};
209
- if (resp.expiry && resp.expiry.value() > 0) {
210
- expiry_time.emplace(std::chrono::seconds{ resp.expiry.value() });
211
- }
212
- obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
213
- return handler(core::impl::make_error(std::move(resp.ctx)),
214
- get_result{ resp.cas,
215
- { std::move(resp.value), resp.flags },
216
- expiry_time,
217
- std::move(crypto_manager) });
218
- });
211
+ return core_.execute(std::move(request),
212
+ [obs_rec = std::move(obs_rec),
213
+ crypto_manager = crypto_manager_,
214
+ handler = std::move(handler)](auto resp) mutable {
215
+ std::optional<std::chrono::system_clock::time_point> expiry_time{};
216
+ if (resp.expiry && resp.expiry.value() > 0) {
217
+ expiry_time.emplace(std::chrono::seconds{ resp.expiry.value() });
218
+ }
219
+ obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
220
+ invoke_with_node_id(std::move(handler),
221
+ core::impl::make_error(std::move(resp.ctx)),
222
+ get_result{ resp.cas,
223
+ { std::move(resp.value), resp.flags },
224
+ expiry_time,
225
+ std::move(crypto_manager) });
226
+ });
219
227
  }
220
228
 
221
229
  void get_and_touch(std::string document_key,
@@ -241,17 +249,18 @@ public:
241
249
  obs_rec->operation_span(),
242
250
  };
243
251
 
244
- return core_.execute(
245
- std::move(request),
246
- [obs_rec = std::move(obs_rec),
247
- crypto_manager = crypto_manager_,
248
- handler = std::move(handler)](auto resp) mutable {
249
- obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
250
- return handler(
251
- core::impl::make_error(std::move(resp.ctx)),
252
- get_result{
253
- resp.cas, { std::move(resp.value), resp.flags }, {}, std::move(crypto_manager) });
254
- });
252
+ return core_.execute(std::move(request),
253
+ [obs_rec = std::move(obs_rec),
254
+ crypto_manager = crypto_manager_,
255
+ handler = std::move(handler)](auto resp) mutable {
256
+ obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
257
+ invoke_with_node_id(std::move(handler),
258
+ core::impl::make_error(std::move(resp.ctx)),
259
+ get_result{ resp.cas,
260
+ { std::move(resp.value), resp.flags },
261
+ {},
262
+ std::move(crypto_manager) });
263
+ });
255
264
  }
256
265
 
257
266
  void touch(std::string document_key,
@@ -280,7 +289,8 @@ public:
280
289
  std::move(request),
281
290
  [obs_rec = std::move(obs_rec), handler = std::move(handler)](const auto& resp) mutable {
282
291
  obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
283
- return handler(core::impl::make_error(std::move(resp.ctx)), result{ resp.cas });
292
+ invoke_with_node_id(
293
+ std::move(handler), core::impl::make_error(std::move(resp.ctx)), result{ resp.cas });
284
294
  });
285
295
  }
286
296
 
@@ -302,20 +312,20 @@ public:
302
312
  options.read_preference,
303
313
  obs_rec->operation_span(),
304
314
  };
305
- return core_.execute(
306
- std::move(request),
307
- [obs_rec = std::move(obs_rec),
308
- crypto_manager = crypto_manager_,
309
- handler = std::move(handler)](auto resp) mutable {
310
- obs_rec->finish(resp.ctx.ec());
311
- return handler(core::impl::make_error(std::move(resp.ctx)),
312
- get_replica_result{
313
- resp.cas,
314
- resp.replica,
315
- { std::move(resp.value), resp.flags },
316
- std::move(crypto_manager),
317
- });
318
- });
315
+ return core_.execute(std::move(request),
316
+ [obs_rec = std::move(obs_rec),
317
+ crypto_manager = crypto_manager_,
318
+ handler = std::move(handler)](auto resp) mutable {
319
+ obs_rec->finish(resp.ctx.ec());
320
+ invoke_with_node_id(std::move(handler),
321
+ core::impl::make_error(std::move(resp.ctx)),
322
+ get_replica_result{
323
+ resp.cas,
324
+ resp.replica,
325
+ { std::move(resp.value), resp.flags },
326
+ std::move(crypto_manager),
327
+ });
328
+ });
319
329
  }
320
330
 
321
331
  void get_all_replicas(std::string document_key,
@@ -336,23 +346,25 @@ public:
336
346
  options.read_preference,
337
347
  obs_rec->operation_span(),
338
348
  };
339
- return core_.execute(
340
- std::move(request),
341
- [obs_rec = std::move(obs_rec),
342
- crypto_manager = crypto_manager_,
343
- handler = std::move(handler)](auto resp) mutable {
344
- get_all_replicas_result result{};
345
- for (auto& entry : resp.entries) {
346
- result.emplace_back(get_replica_result{
347
- entry.cas,
348
- entry.replica,
349
- { std::move(entry.value), entry.flags },
350
- crypto_manager,
351
- });
352
- }
353
- obs_rec->finish(resp.ctx.ec());
354
- return handler(core::impl::make_error(std::move(resp.ctx)), std::move(result));
355
- });
349
+ return core_.execute(std::move(request),
350
+ [obs_rec = std::move(obs_rec),
351
+ crypto_manager = crypto_manager_,
352
+ handler = std::move(handler)](auto resp) mutable {
353
+ get_all_replicas_result result{};
354
+ for (auto& entry : resp.entries) {
355
+ get_replica_result replica_result{
356
+ entry.cas,
357
+ entry.replica,
358
+ { std::move(entry.value), entry.flags },
359
+ crypto_manager,
360
+ };
361
+ replica_result.node_id(std::move(entry.dispatched_to_node_id));
362
+ result.emplace_back(std::move(replica_result));
363
+ }
364
+ obs_rec->finish(resp.ctx.ec());
365
+ return handler(core::impl::make_error(std::move(resp.ctx)),
366
+ std::move(result));
367
+ });
356
368
  }
357
369
 
358
370
  void remove(std::string document_key,
@@ -384,10 +396,13 @@ public:
384
396
  [obs_rec = std::move(obs_rec), handler = std::move(handler)](auto resp) mutable {
385
397
  obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
386
398
  if (resp.ctx.ec()) {
387
- return handler(core::impl::make_error(std::move(resp.ctx)), mutation_result{});
399
+ invoke_with_node_id(
400
+ std::move(handler), core::impl::make_error(std::move(resp.ctx)), mutation_result{});
401
+ return;
388
402
  }
389
- return handler(core::impl::make_error(std::move(resp.ctx)),
390
- mutation_result{ resp.cas, std::move(resp.token) });
403
+ invoke_with_node_id(std::move(handler),
404
+ core::impl::make_error(std::move(resp.ctx)),
405
+ mutation_result{ resp.cas, std::move(resp.token) });
391
406
  });
392
407
  }
393
408
 
@@ -410,8 +425,10 @@ public:
410
425
  handler = std::move(handler)](auto&& resp) mutable {
411
426
  if (resp.ctx.ec()) {
412
427
  obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
413
- return handler(core::impl::make_error(std::move(resp.ctx)),
414
- mutation_result{ resp.cas, std::move(resp.token) });
428
+ invoke_with_node_id(std::move(handler),
429
+ core::impl::make_error(std::move(resp.ctx)),
430
+ mutation_result{ resp.cas, std::move(resp.token) });
431
+ return;
415
432
  }
416
433
  auto token = resp.token;
417
434
  core::impl::initiate_observe_poll(
@@ -426,10 +443,13 @@ public:
426
443
  obs_rec->finish(resp.ctx.retry_attempts(), ec);
427
444
  if (ec) {
428
445
  resp.ctx.override_ec(ec);
429
- return handler(core::impl::make_error(std::move(resp.ctx)), mutation_result{});
446
+ invoke_with_node_id(
447
+ std::move(handler), core::impl::make_error(std::move(resp.ctx)), mutation_result{});
448
+ return;
430
449
  }
431
- return handler(core::impl::make_error(std::move(resp.ctx)),
432
- mutation_result{ resp.cas, std::move(resp.token) });
450
+ invoke_with_node_id(std::move(handler),
451
+ core::impl::make_error(std::move(resp.ctx)),
452
+ mutation_result{ resp.cas, std::move(resp.token) });
433
453
  });
434
454
  });
435
455
  }
@@ -456,17 +476,18 @@ public:
456
476
  { options.retry_strategy },
457
477
  obs_rec->operation_span(),
458
478
  };
459
- core_.execute(
460
- std::move(request),
461
- [obs_rec = std::move(obs_rec),
462
- crypto_manager = crypto_manager_,
463
- handler = std::move(handler)](auto&& resp) mutable {
464
- obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
465
- return handler(
466
- core::impl::make_error(std::move(resp.ctx)),
467
- get_result{
468
- resp.cas, { std::move(resp.value), resp.flags }, {}, std::move(crypto_manager) });
469
- });
479
+ core_.execute(std::move(request),
480
+ [obs_rec = std::move(obs_rec),
481
+ crypto_manager = crypto_manager_,
482
+ handler = std::move(handler)](auto&& resp) mutable {
483
+ obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
484
+ invoke_with_node_id(std::move(handler),
485
+ core::impl::make_error(std::move(resp.ctx)),
486
+ get_result{ resp.cas,
487
+ { std::move(resp.value), resp.flags },
488
+ {},
489
+ std::move(crypto_manager) });
490
+ });
470
491
  }
471
492
 
472
493
  void unlock(std::string document_key,
@@ -523,8 +544,9 @@ public:
523
544
  std::move(request),
524
545
  [obs_rec = std::move(obs_rec), handler = std::move(handler)](auto&& resp) mutable {
525
546
  obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
526
- return handler(core::impl::make_error(std::move(resp.ctx)),
527
- exists_result{ resp.cas, resp.exists() });
547
+ invoke_with_node_id(std::move(handler),
548
+ core::impl::make_error(std::move(resp.ctx)),
549
+ exists_result{ resp.cas, resp.exists() });
528
550
  });
529
551
  }
530
552
 
@@ -557,7 +579,9 @@ public:
557
579
  obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
558
580
 
559
581
  if (resp.ctx.ec()) {
560
- return handler(core::impl::make_error(std::move(resp.ctx)), lookup_in_result{});
582
+ invoke_with_node_id(
583
+ std::move(handler), core::impl::make_error(std::move(resp.ctx)), lookup_in_result{});
584
+ return;
561
585
  }
562
586
 
563
587
  std::vector<lookup_in_result::entry> entries{};
@@ -571,8 +595,9 @@ public:
571
595
  entry.ec,
572
596
  });
573
597
  }
574
- return handler(core::impl::make_error(std::move(resp.ctx)),
575
- lookup_in_result{ resp.cas, std::move(entries), resp.deleted });
598
+ invoke_with_node_id(std::move(handler),
599
+ core::impl::make_error(std::move(resp.ctx)),
600
+ lookup_in_result{ resp.cas, std::move(entries), resp.deleted });
576
601
  });
577
602
  }
578
603
 
@@ -612,15 +637,17 @@ public:
612
637
  field.ec,
613
638
  });
614
639
  }
615
- result.emplace_back(lookup_in_replica_result{
640
+ lookup_in_replica_result replica_result{
616
641
  res.cas,
617
642
  std::move(entries),
618
643
  res.deleted,
619
644
  res.is_replica,
620
- });
645
+ };
646
+ replica_result.node_id(std::move(res.dispatched_to_node_id));
647
+ result.emplace_back(std::move(replica_result));
621
648
  }
622
649
  obs_rec->finish(resp.ctx.ec());
623
- return handler(core::impl::make_error(std::move(resp.ctx)), result);
650
+ return handler(core::impl::make_error(std::move(resp.ctx)), std::move(result));
624
651
  });
625
652
  }
626
653
 
@@ -659,7 +686,8 @@ public:
659
686
  });
660
687
  }
661
688
  obs_rec->finish(resp.ctx.ec());
662
- return handler(
689
+ invoke_with_node_id(
690
+ std::move(handler),
663
691
  core::impl::make_error(std::move(resp.ctx)),
664
692
  lookup_in_replica_result{ resp.cas, std::move(entries), resp.deleted, resp.is_replica });
665
693
  });
@@ -702,7 +730,9 @@ public:
702
730
  [obs_rec = std::move(obs_rec), handler = std::move(handler)](auto resp) mutable {
703
731
  obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
704
732
  if (resp.ctx.ec()) {
705
- return handler(core::impl::make_error(std::move(resp.ctx)), mutate_in_result{});
733
+ invoke_with_node_id(
734
+ std::move(handler), core::impl::make_error(std::move(resp.ctx)), mutate_in_result{});
735
+ return;
706
736
  }
707
737
  std::vector<mutate_in_result::entry> entries{};
708
738
  entries.reserve(resp.fields.size());
@@ -713,7 +743,8 @@ public:
713
743
  entry.original_index,
714
744
  });
715
745
  }
716
- return handler(
746
+ invoke_with_node_id(
747
+ std::move(handler),
717
748
  core::impl::make_error(std::move(resp.ctx)),
718
749
  mutate_in_result{ resp.cas, std::move(resp.token), std::move(entries), resp.deleted });
719
750
  });
@@ -736,47 +767,55 @@ public:
736
767
  options.preserve_expiry,
737
768
  obs_rec->operation_span(),
738
769
  };
739
- return core_.execute(
740
- std::move(request),
741
- [obs_rec = std::move(obs_rec),
742
- core = core_,
743
- id = std::move(id),
744
- options,
745
- handler = std::move(handler)](auto&& resp) mutable {
746
- if (resp.ctx.ec()) {
747
- obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
748
- return handler(core::impl::make_error(std::move(resp.ctx)), mutate_in_result{});
749
- }
770
+ return core_.execute(std::move(request),
771
+ [obs_rec = std::move(obs_rec),
772
+ core = core_,
773
+ id = std::move(id),
774
+ options,
775
+ handler = std::move(handler)](auto&& resp) mutable {
776
+ if (resp.ctx.ec()) {
777
+ obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
778
+ invoke_with_node_id(std::move(handler),
779
+ core::impl::make_error(std::move(resp.ctx)),
780
+ mutate_in_result{});
781
+ return;
782
+ }
750
783
 
751
- auto token = resp.token;
752
- core::impl::initiate_observe_poll(
753
- core,
754
- std::move(id),
755
- token,
756
- options.timeout,
757
- options.persist_to,
758
- options.replicate_to,
759
- [obs_rec = std::move(obs_rec), resp, handler = std::move(handler)](
760
- std::error_code ec) mutable {
761
- obs_rec->finish(resp.ctx.retry_attempts(), ec);
762
- if (ec) {
763
- resp.ctx.override_ec(ec);
764
- return handler(core::impl::make_error(std::move(resp.ctx)), mutate_in_result{});
765
- }
766
- std::vector<mutate_in_result::entry> entries{};
767
- entries.reserve(resp.fields.size());
768
- for (auto& entry : resp.fields) {
769
- entries.emplace_back(mutate_in_result::entry{
770
- std::move(entry.path),
771
- std::move(entry.value),
772
- entry.original_index,
773
- });
774
- }
775
- return handler(core::impl::make_error(std::move(resp.ctx)),
776
- mutate_in_result{
777
- resp.cas, std::move(resp.token), std::move(entries), resp.deleted });
778
- });
779
- });
784
+ auto token = resp.token;
785
+ core::impl::initiate_observe_poll(
786
+ core,
787
+ std::move(id),
788
+ token,
789
+ options.timeout,
790
+ options.persist_to,
791
+ options.replicate_to,
792
+ [obs_rec = std::move(obs_rec), resp, handler = std::move(handler)](
793
+ std::error_code ec) mutable {
794
+ obs_rec->finish(resp.ctx.retry_attempts(), ec);
795
+ if (ec) {
796
+ resp.ctx.override_ec(ec);
797
+ invoke_with_node_id(std::move(handler),
798
+ core::impl::make_error(std::move(resp.ctx)),
799
+ mutate_in_result{});
800
+ return;
801
+ }
802
+ std::vector<mutate_in_result::entry> entries{};
803
+ entries.reserve(resp.fields.size());
804
+ for (auto& entry : resp.fields) {
805
+ entries.emplace_back(mutate_in_result::entry{
806
+ std::move(entry.path),
807
+ std::move(entry.value),
808
+ entry.original_index,
809
+ });
810
+ }
811
+ invoke_with_node_id(std::move(handler),
812
+ core::impl::make_error(std::move(resp.ctx)),
813
+ mutate_in_result{ resp.cas,
814
+ std::move(resp.token),
815
+ std::move(entries),
816
+ resp.deleted });
817
+ });
818
+ });
780
819
  }
781
820
 
782
821
  void upsert(std::string document_key,
@@ -812,8 +851,9 @@ public:
812
851
  std::move(request),
813
852
  [obs_rec = std::move(obs_rec), handler = std::move(handler)](auto resp) mutable {
814
853
  obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
815
- return handler(core::impl::make_error(std::move(resp.ctx)),
816
- mutation_result{ resp.cas, std::move(resp.token) });
854
+ invoke_with_node_id(std::move(handler),
855
+ core::impl::make_error(std::move(resp.ctx)),
856
+ mutation_result{ resp.cas, std::move(resp.token) });
817
857
  });
818
858
  }
819
859
 
@@ -839,8 +879,10 @@ public:
839
879
  handler = std::move(handler)](auto resp) mutable {
840
880
  if (resp.ctx.ec()) {
841
881
  obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
842
- return handler(core::impl::make_error(std::move(resp.ctx)),
843
- mutation_result{ resp.cas, std::move(resp.token) });
882
+ invoke_with_node_id(std::move(handler),
883
+ core::impl::make_error(std::move(resp.ctx)),
884
+ mutation_result{ resp.cas, std::move(resp.token) });
885
+ return;
844
886
  }
845
887
 
846
888
  auto token = resp.token;
@@ -856,10 +898,13 @@ public:
856
898
  obs_rec->finish(resp.ctx.retry_attempts(), ec);
857
899
  if (ec) {
858
900
  resp.ctx.override_ec(ec);
859
- return handler(core::impl::make_error(std::move(resp.ctx)), mutation_result{});
901
+ invoke_with_node_id(
902
+ std::move(handler), core::impl::make_error(std::move(resp.ctx)), mutation_result{});
903
+ return;
860
904
  }
861
- return handler(core::impl::make_error(std::move(resp.ctx)),
862
- mutation_result{ resp.cas, std::move(resp.token) });
905
+ invoke_with_node_id(std::move(handler),
906
+ core::impl::make_error(std::move(resp.ctx)),
907
+ mutation_result{ resp.cas, std::move(resp.token) });
863
908
  });
864
909
  });
865
910
  }
@@ -897,10 +942,13 @@ public:
897
942
  [obs_rec = std::move(obs_rec), handler = std::move(handler)](auto&& resp) mutable {
898
943
  obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
899
944
  if (resp.ctx.ec()) {
900
- return handler(core::impl::make_error(std::move(resp.ctx)), mutation_result{});
945
+ invoke_with_node_id(
946
+ std::move(handler), core::impl::make_error(std::move(resp.ctx)), mutation_result{});
947
+ return;
901
948
  }
902
- return handler(core::impl::make_error(std::move(resp.ctx)),
903
- mutation_result{ resp.cas, std::move(resp.token) });
949
+ invoke_with_node_id(std::move(handler),
950
+ core::impl::make_error(std::move(resp.ctx)),
951
+ mutation_result{ resp.cas, std::move(resp.token) });
904
952
  });
905
953
  }
906
954
 
@@ -925,8 +973,10 @@ public:
925
973
  handler = std::move(handler)](auto resp) mutable {
926
974
  if (resp.ctx.ec()) {
927
975
  obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
928
- return handler(core::impl::make_error(std::move(resp.ctx)),
929
- mutation_result{ resp.cas, std::move(resp.token) });
976
+ invoke_with_node_id(std::move(handler),
977
+ core::impl::make_error(std::move(resp.ctx)),
978
+ mutation_result{ resp.cas, std::move(resp.token) });
979
+ return;
930
980
  }
931
981
 
932
982
  auto token = resp.token;
@@ -942,10 +992,13 @@ public:
942
992
  obs_rec->finish(resp.ctx.retry_attempts(), ec);
943
993
  if (ec) {
944
994
  resp.ctx.override_ec(ec);
945
- return handler(core::impl::make_error(std::move(resp.ctx)), mutation_result{});
995
+ invoke_with_node_id(
996
+ std::move(handler), core::impl::make_error(std::move(resp.ctx)), mutation_result{});
997
+ return;
946
998
  }
947
- return handler(core::impl::make_error(std::move(resp.ctx)),
948
- mutation_result{ resp.cas, std::move(resp.token) });
999
+ invoke_with_node_id(std::move(handler),
1000
+ core::impl::make_error(std::move(resp.ctx)),
1001
+ mutation_result{ resp.cas, std::move(resp.token) });
949
1002
  });
950
1003
  });
951
1004
  }
@@ -986,10 +1039,13 @@ public:
986
1039
  [obs_rec = std::move(obs_rec), handler = std::move(handler)](auto resp) mutable {
987
1040
  obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
988
1041
  if (resp.ctx.ec()) {
989
- return handler(core::impl::make_error(std::move(resp.ctx)), mutation_result{});
1042
+ invoke_with_node_id(
1043
+ std::move(handler), core::impl::make_error(std::move(resp.ctx)), mutation_result{});
1044
+ return;
990
1045
  }
991
- return handler(core::impl::make_error(std::move(resp.ctx)),
992
- mutation_result{ resp.cas, std::move(resp.token) });
1046
+ invoke_with_node_id(std::move(handler),
1047
+ core::impl::make_error(std::move(resp.ctx)),
1048
+ mutation_result{ resp.cas, std::move(resp.token) });
993
1049
  });
994
1050
  }
995
1051
 
@@ -1007,38 +1063,43 @@ public:
1007
1063
  options.preserve_expiry,
1008
1064
  obs_rec->operation_span(),
1009
1065
  };
1010
- return core_.execute(std::move(request),
1011
- [obs_rec = std::move(obs_rec),
1012
- core = core_,
1013
- id = std::move(id),
1014
- options,
1015
- handler = std::move(handler)](auto&& resp) mutable {
1016
- if (resp.ctx.ec()) {
1017
- obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
1018
- return handler(core::impl::make_error(std::move(resp.ctx)),
1019
- mutation_result{ resp.cas, std::move(resp.token) });
1020
- }
1066
+ return core_.execute(
1067
+ std::move(request),
1068
+ [obs_rec = std::move(obs_rec),
1069
+ core = core_,
1070
+ id = std::move(id),
1071
+ options,
1072
+ handler = std::move(handler)](auto&& resp) mutable {
1073
+ if (resp.ctx.ec()) {
1074
+ obs_rec->finish(resp.ctx.retry_attempts(), resp.ctx.ec());
1075
+ invoke_with_node_id(std::move(handler),
1076
+ core::impl::make_error(std::move(resp.ctx)),
1077
+ mutation_result{ resp.cas, std::move(resp.token) });
1078
+ return;
1079
+ }
1021
1080
 
1022
- auto token = resp.token;
1023
- core::impl::initiate_observe_poll(
1024
- core,
1025
- std::move(id),
1026
- token,
1027
- options.timeout,
1028
- options.persist_to,
1029
- options.replicate_to,
1030
- [obs_rec = std::move(obs_rec), resp, handler = std::move(handler)](
1031
- std::error_code ec) mutable {
1032
- obs_rec->finish(resp.ctx.retry_attempts(), ec);
1033
- if (ec) {
1034
- resp.ctx.override_ec(ec);
1035
- return handler(core::impl::make_error(std::move(resp.ctx)),
1036
- mutation_result{});
1037
- }
1038
- return handler(core::impl::make_error(std::move(resp.ctx)),
1039
- mutation_result{ resp.cas, std::move(resp.token) });
1040
- });
1041
- });
1081
+ auto token = resp.token;
1082
+ core::impl::initiate_observe_poll(
1083
+ core,
1084
+ std::move(id),
1085
+ token,
1086
+ options.timeout,
1087
+ options.persist_to,
1088
+ options.replicate_to,
1089
+ [obs_rec = std::move(obs_rec), resp, handler = std::move(handler)](
1090
+ std::error_code ec) mutable {
1091
+ obs_rec->finish(resp.ctx.retry_attempts(), ec);
1092
+ if (ec) {
1093
+ resp.ctx.override_ec(ec);
1094
+ invoke_with_node_id(
1095
+ std::move(handler), core::impl::make_error(std::move(resp.ctx)), mutation_result{});
1096
+ return;
1097
+ }
1098
+ invoke_with_node_id(std::move(handler),
1099
+ core::impl::make_error(std::move(resp.ctx)),
1100
+ mutation_result{ resp.cas, std::move(resp.token) });
1101
+ });
1102
+ });
1042
1103
  }
1043
1104
 
1044
1105
  void scan(scan_type::built scan_type, scan_options::built options, scan_handler&& handler) const
@@ -1179,6 +1240,134 @@ public:
1179
1240
  });
1180
1241
  }
1181
1242
 
1243
+ void node_id_for(std::string document_key,
1244
+ const node_id_for_options::built& options,
1245
+ node_id_for_handler&& handler) const
1246
+ {
1247
+ auto [origin_ec, origin] = core_.origin();
1248
+ if (origin_ec) {
1249
+ return handler(error{ origin_ec }, node_id{});
1250
+ }
1251
+ const bool is_tls = origin.options().enable_tls;
1252
+ const auto timeout = options.timeout.value_or(origin.options().key_value_timeout);
1253
+
1254
+ // Shared state so that only one of {timeout timer, bucket-config callback}
1255
+ // can invoke the user handler. with_bucket_configuration() is not
1256
+ // cancellable — if config never arrives, the timer is the only way to
1257
+ // guarantee the handler completes.
1258
+ struct state {
1259
+ std::atomic<bool> done{ false };
1260
+ asio::steady_timer timer;
1261
+ node_id_for_handler handler;
1262
+
1263
+ state(asio::io_context& ioc, node_id_for_handler&& h)
1264
+ : timer{ ioc }
1265
+ , handler{ std::move(h) }
1266
+ {
1267
+ }
1268
+ };
1269
+ auto shared = std::make_shared<state>(core_.io_context(), std::move(handler));
1270
+
1271
+ shared->timer.expires_after(timeout);
1272
+ shared->timer.async_wait([shared](std::error_code timer_ec) -> void {
1273
+ if (timer_ec == asio::error::operation_aborted) {
1274
+ return;
1275
+ }
1276
+ if (shared->done.exchange(true)) {
1277
+ return;
1278
+ }
1279
+ shared->handler(error{ errc::common::unambiguous_timeout }, node_id{});
1280
+ });
1281
+
1282
+ core_.with_bucket_configuration(
1283
+ bucket_name_,
1284
+ [shared, document_key = std::move(document_key), is_tls](
1285
+ std::error_code ec, const std::shared_ptr<core::topology::configuration>& config) -> void {
1286
+ if (shared->done.exchange(true)) {
1287
+ return;
1288
+ }
1289
+ shared->timer.cancel();
1290
+ if (ec) {
1291
+ return shared->handler(error{ ec }, node_id{});
1292
+ }
1293
+ if (!config || !config->vbmap.has_value() || config->vbmap->empty()) {
1294
+ return shared->handler(error{ errc::network::configuration_not_available }, node_id{});
1295
+ }
1296
+ // Only server_index is needed here; vbucket is intentionally unused.
1297
+ // Binding it as [[maybe_unused]] would require C++20 structured-
1298
+ // binding attribute support, so take the pair by value and pull the
1299
+ // field we need instead.
1300
+ const auto mapping = config->map_key(document_key, 0);
1301
+ const auto& server_index = mapping.second;
1302
+ if (!server_index.has_value() || server_index.value() >= config->nodes.size()) {
1303
+ // An unresolvable server_index means the vBucket map is present
1304
+ // but does not map this key to a known node (out-of-range or
1305
+ // missing entry). Surface this as configuration_not_available
1306
+ // rather than request_canceled so callers can distinguish an
1307
+ // unusable map from an actual caller-initiated cancellation.
1308
+ return shared->handler(error{ errc::network::configuration_not_available }, node_id{});
1309
+ }
1310
+ return shared->handler({}, config->nodes[server_index.value()].effective_node_id(is_tls));
1311
+ });
1312
+ }
1313
+
1314
+ void node_ids(const node_ids_options::built& options, node_ids_handler&& handler) const
1315
+ {
1316
+ auto [origin_ec, origin] = core_.origin();
1317
+ if (origin_ec) {
1318
+ return handler(error{ origin_ec }, {});
1319
+ }
1320
+ const bool is_tls = origin.options().enable_tls;
1321
+ const auto timeout = options.timeout.value_or(origin.options().key_value_timeout);
1322
+
1323
+ // Same shape as node_id_for: with_bucket_configuration() is not
1324
+ // cancellable, so a steady_timer guarded by an atomic done-flag is
1325
+ // the only way to bound the wait when no config arrives.
1326
+ struct state {
1327
+ std::atomic<bool> done{ false };
1328
+ asio::steady_timer timer;
1329
+ node_ids_handler handler;
1330
+
1331
+ state(asio::io_context& ioc, node_ids_handler&& h)
1332
+ : timer{ ioc }
1333
+ , handler{ std::move(h) }
1334
+ {
1335
+ }
1336
+ };
1337
+ auto shared = std::make_shared<state>(core_.io_context(), std::move(handler));
1338
+
1339
+ shared->timer.expires_after(timeout);
1340
+ shared->timer.async_wait([shared](std::error_code timer_ec) -> void {
1341
+ if (timer_ec == asio::error::operation_aborted) {
1342
+ return;
1343
+ }
1344
+ if (shared->done.exchange(true)) {
1345
+ return;
1346
+ }
1347
+ shared->handler(error{ errc::common::unambiguous_timeout }, {});
1348
+ });
1349
+
1350
+ core_.with_bucket_configuration(
1351
+ bucket_name_,
1352
+ [shared, is_tls](std::error_code ec,
1353
+ const std::shared_ptr<core::topology::configuration>& config) -> void {
1354
+ if (shared->done.exchange(true)) {
1355
+ return;
1356
+ }
1357
+ shared->timer.cancel();
1358
+ if (ec) {
1359
+ return shared->handler(error{ ec }, {});
1360
+ }
1361
+ if (!config) {
1362
+ // No config arrived but no error either — this should not
1363
+ // happen in practice, but surface it the same way node_id_for
1364
+ // does so callers can distinguish from request_canceled.
1365
+ return shared->handler(error{ errc::network::configuration_not_available }, {});
1366
+ }
1367
+ return shared->handler({}, config->effective_node_ids(is_tls));
1368
+ });
1369
+ }
1370
+
1182
1371
  private:
1183
1372
  static auto get_encoded_value(
1184
1373
  std::variant<codec::encoded_value, std::function<codec::encoded_value()>> value,
@@ -1269,6 +1458,44 @@ collection::crypto_manager() const -> const std::shared_ptr<crypto::manager>&
1269
1458
  return impl_->crypto_manager();
1270
1459
  }
1271
1460
 
1461
+ void
1462
+ collection::node_id_for(std::string document_id,
1463
+ const node_id_for_options& options,
1464
+ node_id_for_handler&& handler) const
1465
+ {
1466
+ return impl_->node_id_for(std::move(document_id), options.build(), std::move(handler));
1467
+ }
1468
+
1469
+ auto
1470
+ collection::node_id_for(std::string document_id, const node_id_for_options& options) const
1471
+ -> std::future<std::pair<error, node_id>>
1472
+ {
1473
+ auto barrier = std::make_shared<std::promise<std::pair<error, node_id>>>();
1474
+ auto future = barrier->get_future();
1475
+ node_id_for(std::move(document_id), options, [barrier](auto err, auto nid) {
1476
+ barrier->set_value({ std::move(err), std::move(nid) });
1477
+ });
1478
+ return future;
1479
+ }
1480
+
1481
+ void
1482
+ collection::node_ids(const node_ids_options& options, node_ids_handler&& handler) const
1483
+ {
1484
+ return impl_->node_ids(options.build(), std::move(handler));
1485
+ }
1486
+
1487
+ auto
1488
+ collection::node_ids(const node_ids_options& options) const
1489
+ -> std::future<std::pair<error, std::vector<node_id>>>
1490
+ {
1491
+ auto barrier = std::make_shared<std::promise<std::pair<error, std::vector<node_id>>>>();
1492
+ auto future = barrier->get_future();
1493
+ node_ids(options, [barrier](auto err, auto nids) {
1494
+ barrier->set_value({ std::move(err), std::move(nids) });
1495
+ });
1496
+ return future;
1497
+ }
1498
+
1272
1499
  void
1273
1500
  collection::get(std::string document_id, const get_options& options, get_handler&& handler) const
1274
1501
  {