@reboot-dev/reboot 0.23.0 → 0.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/reboot_native.cc CHANGED
@@ -81,6 +81,23 @@ struct PythonNodeAdaptor {
81
81
 
82
82
  template <typename F>
83
83
  void ScheduleCallbackOnNodeEventLoop(F&& f) {
84
+ // It is possible that Node.js is shutting down and has already
85
+ // called the `thread_safe_function` finalizer and trying to use
86
+ // `thread_safe_function` will raise SIGABRT or SIGSEGV.
87
+ //
88
+ // Given that we're shutting down we just return!
89
+ //
90
+ // If the callback is to free memory, e.g., from a
91
+ // `NapiSafeReference`, that's not a problem because we're
92
+ // shutting down so all the memory will get freed then! ;-)
93
+ //
94
+ // If this is for some other critical function that is necessary
95
+ // to do _before_ shutting down then we'll need to rethink this
96
+ // approaach.
97
+ if (thread_safe_function_finalized) {
98
+ return;
99
+ }
100
+
84
101
  napi_status status = thread_safe_function.BlockingCall(
85
102
  [f = std::forward<F>(f)](
86
103
  Napi::Env env,
@@ -116,6 +133,8 @@ struct PythonNodeAdaptor {
116
133
 
117
134
  Napi::ThreadSafeFunction thread_safe_function;
118
135
 
136
+ bool thread_safe_function_finalized = false;
137
+
119
138
  std::thread thread;
120
139
  std::mutex mutex;
121
140
  std::condition_variable python_functions_not_empty;
@@ -133,6 +152,83 @@ static Napi::FunctionReference* js_launchSubprocessConsensus =
133
152
  new Napi::FunctionReference();
134
153
 
135
154
 
155
+ struct NapiReferenceDeleter {
156
+ template <typename T>
157
+ void operator()(Napi::Reference<T>* reference) {
158
+ adaptor->ScheduleCallbackOnNodeEventLoop(
159
+ [reference](Napi::Env) {
160
+ delete reference;
161
+ });
162
+ }
163
+ };
164
+
165
+
166
+ template <typename T>
167
+ class NapiSafeReference {
168
+ public:
169
+ NapiSafeReference(T t)
170
+ : _reference(
171
+ new Napi::Reference<T>(Napi::Persistent(std::move(t))),
172
+ NapiReferenceDeleter()) {}
173
+
174
+ NapiSafeReference(Napi::Reference<T>&& reference)
175
+ : _reference(
176
+ new Napi::Reference<T>(std::move(reference)),
177
+ NapiReferenceDeleter()) {}
178
+
179
+ // Helper for getting the value of the reference. We require a
180
+ // `Napi::Env` to ensure we only try and get the value from within a
181
+ // Node thread (e.g., using the main thread or from within a NAPI
182
+ // thread safe function).
183
+ T Value(Napi::Env) const {
184
+ return _reference->Value();
185
+ }
186
+
187
+ private:
188
+ std::shared_ptr<Napi::Reference<T>> _reference;
189
+ };
190
+
191
+ using NapiSafeFunctionReference = NapiSafeReference<Napi::Function>;
192
+ using NapiSafeObjectReference = NapiSafeReference<Napi::Object>;
193
+
194
+ // For every servicer we construct a _subclass_ of our Python
195
+ // generated Node adaptor, e.g., `GreeterServicerNodeAdaptor`, _at
196
+ // runtime_ which we add to the following Python module. This module
197
+ // also includes our C++ <-> Python class wrappers for storing NAPI
198
+ // objects in Python to properly handle memory management.
199
+ PYBIND11_EMBEDDED_MODULE(reboot_native, m) {
200
+ py::class_<NapiSafeFunctionReference>(
201
+ m,
202
+ "NapiSafeFunctionReference");
203
+
204
+ py::class_<NapiSafeObjectReference>(
205
+ m,
206
+ "NapiSafeObjectReference");
207
+ }
208
+
209
+
210
+ Napi::External<py::object> make_napi_external(
211
+ Napi::Env env,
212
+ py::object* py_object,
213
+ const napi_type_tag* type_tag = nullptr) {
214
+ auto js_external = Napi::External<py::object>::New(
215
+ env,
216
+ py_object,
217
+ [](Napi::Env, py::object* py_object) {
218
+ adaptor->ScheduleCallbackOnPythonEventLoop(
219
+ [py_object]() {
220
+ delete py_object;
221
+ });
222
+ });
223
+
224
+ if (type_tag != nullptr) {
225
+ js_external.TypeTag(type_tag);
226
+ }
227
+
228
+ return js_external;
229
+ }
230
+
231
+
136
232
  void PythonNodeAdaptor::Initialize(
137
233
  Napi::Env& env,
138
234
  const Napi::Function& js_callback) {
@@ -143,8 +239,12 @@ void PythonNodeAdaptor::Initialize(
143
239
  /* Max queue size (0 = unlimited): */ 0,
144
240
  /* Initial thread count: */ 1,
145
241
  /* Context: */ this,
146
- /* Finalizer: */
147
- [](Napi::Env env, void*, PythonNodeAdaptor*) {},
242
+ // Finalizer:
243
+ [this](Napi::Env env, void*, PythonNodeAdaptor*) {
244
+ // Set that we've been finalized so that we don't use
245
+ // `thread_safe_function` again, see comment in
246
+ // `ScheduleCallbackOnNodeEventLoop`.
247
+ thread_safe_function_finalized = true; },
148
248
  /* Finalizer data: */ (void*) nullptr);
149
249
 
150
250
  // We start with our thread safe function unreferenced so that
@@ -301,6 +401,476 @@ std::string message_from_js_error(const Napi::Object& js_error) {
301
401
  }
302
402
 
303
403
 
404
+ template <typename F, typename T = std::invoke_result_t<F>>
405
+ T RunCallbackOnPythonEventLoop(F&& f) {
406
+ std::promise<T> promise;
407
+
408
+ adaptor->ScheduleCallbackOnPythonEventLoop(
409
+ [&f, &promise]() {
410
+ try {
411
+ if constexpr (std::is_void<T>::value) {
412
+ f();
413
+ promise.set_value();
414
+ } else {
415
+ promise.set_value(f());
416
+ }
417
+ } catch (const std::exception& e) {
418
+ promise.set_exception(
419
+ std::make_exception_ptr(
420
+ std::runtime_error(e.what())));
421
+ }
422
+ });
423
+
424
+ return promise.get_future().get();
425
+ }
426
+
427
+
428
+ template <typename F, typename T = std::invoke_result_t<F, Napi::Env>>
429
+ T RunCallbackOnNodeEventLoop(F&& f, bool warn = false) {
430
+ std::promise<T> promise;
431
+
432
+ adaptor->ScheduleCallbackOnNodeEventLoop(
433
+ [&f, &warn, &promise](Napi::Env env) {
434
+ try {
435
+ if constexpr (std::is_void<T>::value) {
436
+ f(env);
437
+ promise.set_value();
438
+ } else {
439
+ promise.set_value(f(env));
440
+ }
441
+ } catch (const Napi::Error& e) {
442
+ if (warn) {
443
+ env.Global()
444
+ .Get("console")
445
+ .As<Napi::Object>()
446
+ .Get("warn")
447
+ .As<Napi::Function>()
448
+ .Call({e.Value(), Napi::String::New(env, "\n")});
449
+ }
450
+ promise.set_exception(
451
+ std::make_exception_ptr(
452
+ std::runtime_error(e.what())));
453
+ } catch (const std::exception& e) {
454
+ if (warn) {
455
+ env.Global()
456
+ .Get("console")
457
+ .As<Napi::Object>()
458
+ .Get("warn")
459
+ .As<Napi::Function>()
460
+ .Call(
461
+ {Napi::String::New(env, e.what()),
462
+ Napi::String::New(env, "\n")});
463
+ }
464
+ promise.set_exception(
465
+ std::make_exception_ptr(
466
+ std::runtime_error(e.what())));
467
+ }
468
+ });
469
+
470
+ return promise.get_future().get();
471
+ }
472
+
473
+
474
+ template <typename PythonCallback, typename NodeCallback>
475
+ Napi::Promise NodePromiseFromPythonCallback(
476
+ Napi::Env env,
477
+ PythonCallback&& python_callback,
478
+ NodeCallback&& node_callback) {
479
+ auto deferred = std::make_shared<Napi::Promise::Deferred>(env);
480
+ auto promise = deferred->Promise();
481
+
482
+ adaptor->ScheduleCallbackOnPythonEventLoop(
483
+ [python_callback = std::forward<PythonCallback>(python_callback),
484
+ node_callback = std::forward<NodeCallback>(node_callback),
485
+ deferred = std::move(deferred)]() mutable {
486
+ try {
487
+ auto result = python_callback();
488
+ adaptor->ScheduleCallbackOnNodeEventLoop(
489
+ [node_callback = std::forward<NodeCallback>(node_callback),
490
+ deferred = std::move(deferred),
491
+ result = std::move(result)](Napi::Env env) mutable {
492
+ try {
493
+ deferred->Resolve(node_callback(env, std::move(result)));
494
+ } catch (const Napi::Error& e) {
495
+ deferred->Reject(e.Value());
496
+ } catch (const std::exception& e) {
497
+ // TODO: is this code unreachable because Node.js
498
+ // will properly always only pass us a `Napi::Error`?
499
+ deferred->Reject(
500
+ Napi::Error::New(env, e.what()).Value());
501
+ } catch (...) {
502
+ // TODO: is this code unreachable because Node.js
503
+ // will properly always only pass us a `Napi::Error`?
504
+ deferred->Reject(
505
+ Napi::Error::New(env, "Unknown error").Value());
506
+ }
507
+ });
508
+ } catch (pybind11::error_already_set& e) {
509
+ std::string exception = py_exception_str(e.value());
510
+ adaptor->ScheduleCallbackOnNodeEventLoop(
511
+ [deferred = std::move(deferred),
512
+ exception = std::move(exception)](Napi::Env env) mutable {
513
+ deferred->Reject(Napi::Error::New(env, exception).Value());
514
+ });
515
+ }
516
+ });
517
+
518
+ return promise;
519
+ }
520
+
521
+
522
+ template <
523
+ typename PythonFutureCallback,
524
+ typename PythonDoneCallback,
525
+ typename NodeCallback>
526
+ Napi::Promise NodePromiseFromPythonFuture(
527
+ Napi::Env env,
528
+ PythonFutureCallback&& python_future_callback,
529
+ PythonDoneCallback&& python_done_callback,
530
+ NodeCallback&& node_callback) {
531
+ auto deferred = std::make_shared<Napi::Promise::Deferred>(env);
532
+ auto promise = deferred->Promise();
533
+
534
+ adaptor->ScheduleCallbackOnPythonEventLoop(
535
+ [python_future_callback = std::forward<PythonFutureCallback>(
536
+ python_future_callback),
537
+ python_done_callback = std::forward<PythonDoneCallback>(
538
+ python_done_callback),
539
+ node_callback = std::forward<NodeCallback>(node_callback),
540
+ deferred = std::move(deferred)]() mutable {
541
+ try {
542
+ // TODO: check that `py_future` is a future!
543
+ py::object py_future = python_future_callback();
544
+
545
+ py_future.attr("add_done_callback")(py::cpp_function(
546
+ [python_done_callback = std::forward<PythonDoneCallback>(
547
+ python_done_callback),
548
+ node_callback = std::forward<NodeCallback>(node_callback),
549
+ deferred = std::move(deferred),
550
+ // NOTE: need to keep a reference to `py_future` so that it
551
+ // doesn't get destroyed before completing!
552
+ py_future = new py::object(py_future)](py::object) mutable {
553
+ py::object py_exception = py_future->attr("exception")();
554
+ if (!py_exception.is_none()) {
555
+ py::str py_exception_string = py_exception_str(py_exception);
556
+ std::string exception = py_exception_string;
557
+
558
+ adaptor->ScheduleCallbackOnNodeEventLoop(
559
+ [deferred = std::move(deferred),
560
+ exception = std::move(exception)](Napi::Env env) {
561
+ deferred->Reject(
562
+ Napi::Error::New(env, exception).Value());
563
+ });
564
+ } else {
565
+ // TODO: try / catch around `python_done_callback`.
566
+ py::object py_result = py_future->attr("result")();
567
+ auto result = python_done_callback(std::move(py_result));
568
+
569
+ adaptor->ScheduleCallbackOnNodeEventLoop(
570
+ [node_callback =
571
+ std::forward<NodeCallback>(node_callback),
572
+ deferred = std::move(deferred),
573
+ result = std::move(result)](Napi::Env env) mutable {
574
+ // TODO: try / catch around `node_callback`.
575
+ auto js_result = node_callback(env, std::move(result));
576
+ deferred->Resolve(js_result);
577
+ });
578
+ }
579
+ delete py_future;
580
+ },
581
+ py::arg("future")));
582
+ } catch (pybind11::error_already_set& e) {
583
+ std::string exception = py_exception_str(e.value());
584
+ adaptor->ScheduleCallbackOnNodeEventLoop(
585
+ [deferred = std::move(deferred),
586
+ exception = std::move(exception)](Napi::Env env) mutable {
587
+ deferred->Reject(Napi::Error::New(env, exception).Value());
588
+ });
589
+ }
590
+ });
591
+
592
+ return promise;
593
+ }
594
+
595
+
596
+ template <typename PythonFutureCallback>
597
+ Napi::Promise NodePromiseFromPythonFuture(
598
+ Napi::Env env,
599
+ PythonFutureCallback&& python_future_callback) {
600
+ return NodePromiseFromPythonFuture(
601
+ env,
602
+ std::forward<PythonFutureCallback>(python_future_callback),
603
+ [](py::object py_result) {
604
+ // TODO: check that `py_result` is `None`.
605
+ return std::nullopt;
606
+ },
607
+ [](Napi::Env env, std::nullopt_t) {
608
+ return env.Null();
609
+ });
610
+ }
611
+
612
+
613
+ template <
614
+ typename PythonTaskCallback,
615
+ typename PythonDoneCallback,
616
+ typename NodeCallback,
617
+ typename... CreateTaskArgs>
618
+ Napi::Promise NodePromiseFromPythonTask(
619
+ Napi::Env env,
620
+ std::string&& name,
621
+ std::tuple<std::string, std::string, CreateTaskArgs...>&& create_task,
622
+ PythonTaskCallback&& python_task_callback,
623
+ PythonDoneCallback&& python_done_callback,
624
+ NodeCallback&& node_callback) {
625
+ return NodePromiseFromPythonFuture(
626
+ env,
627
+ [name = std::move(name),
628
+ create_task = std::move(create_task),
629
+ python_task_callback = std::forward<PythonTaskCallback>(
630
+ python_task_callback)]() mutable {
631
+ py::object py_task = std::apply(
632
+ [&name, &python_task_callback](
633
+ const std::string& import,
634
+ const std::string& attr,
635
+ auto&&... args) mutable {
636
+ return py::module::import(import.c_str())
637
+ .attr(attr.c_str())(
638
+ python_task_callback(),
639
+ std::forward<decltype(args)>(args)...,
640
+ "name"_a = name.c_str());
641
+ },
642
+ std::move(create_task));
643
+
644
+ return py_task;
645
+ },
646
+ std::forward<PythonDoneCallback>(python_done_callback),
647
+ std::forward<NodeCallback>(node_callback));
648
+ }
649
+
650
+
651
+ template <typename PythonTaskCallback, typename... CreateTaskArgs>
652
+ Napi::Promise NodePromiseFromPythonTask(
653
+ Napi::Env env,
654
+ std::string&& name,
655
+ std::tuple<std::string, std::string, CreateTaskArgs...>&& create_task,
656
+ PythonTaskCallback&& python_task_callback) {
657
+ return NodePromiseFromPythonTask(
658
+ env,
659
+ std::move(name),
660
+ std::move(create_task),
661
+ std::forward<PythonTaskCallback>(python_task_callback),
662
+ [](py::object py_result) {
663
+ // TODO: check that `py_result` is `None`.
664
+ return std::nullopt;
665
+ },
666
+ [](Napi::Env env, std::nullopt_t) {
667
+ return env.Null();
668
+ });
669
+ }
670
+
671
+
672
+ template <
673
+ typename PythonTaskCallback,
674
+ typename PythonDoneCallback,
675
+ typename NodeCallback>
676
+ Napi::Promise NodePromiseFromPythonTaskWithContext(
677
+ Napi::Env env,
678
+ std::string&& name,
679
+ NapiSafeReference<Napi::External<py::object>>& js_context,
680
+ PythonTaskCallback&& python_task_callback,
681
+ PythonDoneCallback&& python_done_callback,
682
+ NodeCallback&& node_callback) {
683
+ py::object* py_context = js_context.Value(env).Data();
684
+ return NodePromiseFromPythonTask(
685
+ env,
686
+ std::move(name),
687
+ std::make_tuple(
688
+ std::string("reboot.nodejs.python"),
689
+ std::string("create_task_with_context"),
690
+ py_context),
691
+ [js_context, // Ensures `py_context` remains valid.
692
+ python_task_callback =
693
+ std::forward<PythonTaskCallback>(python_task_callback)]() mutable {
694
+ return python_task_callback();
695
+ },
696
+ std::forward<PythonDoneCallback>(python_done_callback),
697
+ std::forward<NodeCallback>(node_callback));
698
+ }
699
+
700
+
701
+ template <typename PythonTaskCallback>
702
+ Napi::Promise NodePromiseFromPythonTaskWithContext(
703
+ Napi::Env env,
704
+ std::string&& name,
705
+ NapiSafeReference<Napi::External<py::object>>& js_context,
706
+ PythonTaskCallback&& python_task_callback) {
707
+ return NodePromiseFromPythonTaskWithContext(
708
+ env,
709
+ std::move(name),
710
+ js_context,
711
+ std::forward<PythonTaskCallback>(python_task_callback),
712
+ [](py::object py_result) {
713
+ // TODO: check that `py_result` is `None`.
714
+ return std::nullopt;
715
+ },
716
+ [](Napi::Env env, std::nullopt_t) {
717
+ return env.Null();
718
+ });
719
+ }
720
+
721
+
722
+ template <
723
+ typename NodePromiseCallback,
724
+ typename NodePromiseResolvedCallback,
725
+ typename PythonCallback>
726
+ py::object PythonFutureFromNodePromise(
727
+ NodePromiseCallback&& node_promise_callback,
728
+ NodePromiseResolvedCallback&& node_promise_resolved_callback,
729
+ PythonCallback&& python_callback) {
730
+ py::object py_future = py::module::import("asyncio").attr("Future")();
731
+
732
+ adaptor->ScheduleCallbackOnNodeEventLoop(
733
+ [node_promise_callback =
734
+ std::forward<NodePromiseCallback>(node_promise_callback),
735
+ node_promise_resolved_callback =
736
+ std::forward<NodePromiseResolvedCallback>(
737
+ node_promise_resolved_callback),
738
+ python_callback = std::forward<PythonCallback>(python_callback),
739
+ py_future = new py::object(py_future)](Napi::Env env) mutable {
740
+ try {
741
+ Napi::Object js_promise = node_promise_callback(env);
742
+
743
+ js_promise
744
+ .Get("then")
745
+ .As<Napi::Function>()
746
+ .Call(
747
+ js_promise,
748
+ {Napi::Function::New(
749
+ env,
750
+ [node_promise_resolved_callback =
751
+ std::forward<NodePromiseResolvedCallback>(
752
+ node_promise_resolved_callback),
753
+ python_callback = std::forward<PythonCallback>(
754
+ python_callback),
755
+ py_future](const Napi::CallbackInfo& info) mutable {
756
+ // TODO: put a try/catch around
757
+ // `node_promise_resolved_callback` to handle
758
+ // any errors.
759
+
760
+ auto result = node_promise_resolved_callback(
761
+ info.Env(),
762
+ info[0]);
763
+
764
+ adaptor->ScheduleCallbackOnPythonEventLoop(
765
+ [python_callback = std::forward<PythonCallback>(
766
+ python_callback),
767
+ py_future,
768
+ result = std::move(result)]() mutable {
769
+ bool cancelled =
770
+ py_future->attr("cancelled")().cast<bool>();
771
+ if (!cancelled) {
772
+ // TODO: put a try/catch around
773
+ // `python_callback` to handle any
774
+ // errors.
775
+ py_future->attr("set_result")(
776
+ python_callback(std::move(result)));
777
+ }
778
+ delete py_future;
779
+ });
780
+ }),
781
+ Napi::Function::New(
782
+ env,
783
+ [py_future](const Napi::CallbackInfo& info) {
784
+ std::string message = message_from_js_error(
785
+ info[0].As<Napi::Object>());
786
+
787
+ adaptor->ScheduleCallbackOnPythonEventLoop(
788
+ [py_future, message = std::move(message)]() {
789
+ bool cancelled =
790
+ py_future->attr("cancelled")()
791
+ .cast<bool>();
792
+ if (!cancelled) {
793
+ py_future->attr("set_exception")(
794
+ py::module::import("builtins")
795
+ .attr("Exception")(
796
+ message));
797
+ }
798
+ delete py_future;
799
+ });
800
+ })});
801
+ } catch (const std::exception& e) {
802
+ // NOTE: we're just catching exception here vs `Napi::Error`
803
+ // since all we care about is `what()` but we're making sure
804
+ // that we'll get all `Napi::Error` with this
805
+ // `static_assert`.
806
+ static_assert(
807
+ std::is_base_of<std::exception, Napi::Error>::value,
808
+ "Expecting `Napi::Error` to be subclass of `std::exception`");
809
+
810
+ std::string message = e.what();
811
+
812
+ adaptor->ScheduleCallbackOnPythonEventLoop(
813
+ [py_future, message = std::move(message)]() {
814
+ bool cancelled =
815
+ py_future->attr("cancelled")().cast<bool>();
816
+ if (!cancelled) {
817
+ py_future->attr("set_exception")(
818
+ py::module::import("builtins")
819
+ .attr("Exception")(message));
820
+ }
821
+ delete py_future;
822
+ });
823
+ } catch (...) {
824
+ // TODO: is this code unreachable because Node.js
825
+ // will properly always only pass us a `Napi::Error`?
826
+ adaptor->ScheduleCallbackOnPythonEventLoop(
827
+ [py_future]() {
828
+ bool cancelled =
829
+ py_future->attr("cancelled")().cast<bool>();
830
+ if (!cancelled) {
831
+ py_future->attr("set_exception")(
832
+ py::module::import("builtins")
833
+ .attr("Exception")("Unknown error"));
834
+ }
835
+ delete py_future;
836
+ });
837
+ }
838
+ });
839
+
840
+ return py_future;
841
+ }
842
+
843
+
844
+ template <
845
+ typename NodePromiseCallback,
846
+ typename NodePromiseResolvedCallback>
847
+ py::object PythonFutureFromNodePromise(
848
+ NodePromiseCallback&& node_promise_callback,
849
+ NodePromiseResolvedCallback&& node_promise_resolved_callback) {
850
+ return PythonFutureFromNodePromise(
851
+ std::forward<NodePromiseCallback>(node_promise_callback),
852
+ std::forward<NodePromiseResolvedCallback>(
853
+ node_promise_resolved_callback),
854
+ [](auto&& result) {
855
+ return std::move(result);
856
+ });
857
+ }
858
+
859
+
860
+ template <typename NodePromiseCallback>
861
+ py::object PythonFutureFromNodePromise(
862
+ NodePromiseCallback&& node_promise_callback) {
863
+ return PythonFutureFromNodePromise(
864
+ std::forward<NodePromiseCallback>(node_promise_callback),
865
+ [](Napi::Env, Napi::Value) {
866
+ return std::nullopt;
867
+ },
868
+ [](auto&&) {
869
+ return py::none();
870
+ });
871
+ }
872
+
873
+
304
874
  void Initialize(const Napi::CallbackInfo& info) {
305
875
  // Ensure we only initialize once, even if nodejs tries to load
306
876
  // the module more than once.
@@ -327,17 +897,11 @@ void ImportPy(const Napi::CallbackInfo& info) {
327
897
  std::string module = info[0].As<Napi::String>().Utf8Value();
328
898
  std::string base64_encoded_rbt_py = info[1].As<Napi::String>().Utf8Value();
329
899
 
330
- std::promise<void> promise;
331
-
332
- adaptor->ScheduleCallbackOnPythonEventLoop(
333
- [&module, &base64_encoded_rbt_py, &promise]() {
900
+ RunCallbackOnPythonEventLoop(
901
+ [&module, &base64_encoded_rbt_py]() {
334
902
  py::module::import("reboot.nodejs.python")
335
903
  .attr("import_py")(module, base64_encoded_rbt_py);
336
-
337
- promise.set_value();
338
904
  });
339
-
340
- promise.get_future().get();
341
905
  }
342
906
 
343
907
 
@@ -401,41 +965,16 @@ std::string uint8array_to_str(const Napi::Uint8Array& arr) {
401
965
 
402
966
 
403
967
  Napi::Value Reboot_constructor(const Napi::CallbackInfo& info) {
404
- std::promise<py::object*> promise;
405
-
406
- adaptor->ScheduleCallbackOnPythonEventLoop(
407
- [&promise]() {
968
+ py::object* py_reboot = RunCallbackOnPythonEventLoop(
969
+ []() {
408
970
  py::object tests = py::module::import("reboot.aio.tests");
409
- promise.set_value(new py::object(tests.attr("Reboot")()));
410
- }
411
- // TODO(benh): improve error handling mechanism to force all
412
- // raised exceptions to be handled.
413
- //
414
- // [](py::error_already_set& e) {
415
-
416
- // },
417
- // [](std::exception& e) {
418
-
419
- // },
420
- // []() {
421
-
422
- // }
423
- );
424
-
425
- py::object* py_reboot = promise.get_future().get();
971
+ return new py::object(tests.attr("Reboot")());
972
+ });
426
973
 
427
- Napi::External<py::object> js_external_reboot =
428
- Napi::External<py::object>::New(
429
- info.Env(),
430
- py_reboot,
431
- [](Napi::Env, py::object* py_reboot) {
432
- adaptor->ScheduleCallbackOnPythonEventLoop(
433
- [py_reboot]() {
434
- delete py_reboot;
435
- });
436
- });
437
-
438
- js_external_reboot.TypeTag(&reboot_aio_tests_Reboot);
974
+ Napi::External<py::object> js_external_reboot = make_napi_external(
975
+ info.Env(),
976
+ py_reboot,
977
+ &reboot_aio_tests_Reboot);
439
978
 
440
979
  return js_external_reboot;
441
980
  }
@@ -468,15 +1007,12 @@ Napi::Value Reboot_createExternalContext(const Napi::CallbackInfo& info) {
468
1007
  app_internal = info[5].As<Napi::Boolean>();
469
1008
  }
470
1009
 
471
- std::promise<py::object*> promise;
472
-
473
- adaptor->ScheduleCallbackOnPythonEventLoop(
1010
+ py::object* py_context = RunCallbackOnPythonEventLoop(
474
1011
  [py_reboot,
475
1012
  &name,
476
1013
  &idempotency_seed,
477
1014
  &bearer_token,
478
- app_internal,
479
- &promise]() {
1015
+ app_internal]() {
480
1016
  py::object py_idempotency_seed = py::none();
481
1017
  if (idempotency_seed.has_value()) {
482
1018
  py_idempotency_seed =
@@ -487,29 +1023,18 @@ Napi::Value Reboot_createExternalContext(const Napi::CallbackInfo& info) {
487
1023
  if (bearer_token.has_value()) {
488
1024
  py_bearer_token = py::str(*bearer_token);
489
1025
  }
490
- promise.set_value(
491
- new py::object(
492
- py_reboot->attr("create_external_context")(
493
- "name"_a = name,
494
- "idempotency_seed"_a = py_idempotency_seed,
495
- "bearer_token"_a = py_bearer_token,
496
- "app_internal"_a = app_internal)));
1026
+ return new py::object(
1027
+ py_reboot->attr("create_external_context")(
1028
+ "name"_a = name,
1029
+ "idempotency_seed"_a = py_idempotency_seed,
1030
+ "bearer_token"_a = py_bearer_token,
1031
+ "app_internal"_a = app_internal));
497
1032
  });
498
1033
 
499
- py::object* py_context = promise.get_future().get();
500
-
501
- Napi::External<py::object> js_external_context =
502
- Napi::External<py::object>::New(
503
- info.Env(),
504
- py_context,
505
- [](Napi::Env, py::object* py_context) {
506
- adaptor->ScheduleCallbackOnPythonEventLoop(
507
- [py_context]() {
508
- delete py_context;
509
- });
510
- });
511
-
512
- js_external_context.TypeTag(&reboot_aio_external_ExternalContext);
1034
+ Napi::External<py::object> js_external_context = make_napi_external(
1035
+ info.Env(),
1036
+ py_context,
1037
+ &reboot_aio_external_ExternalContext);
513
1038
 
514
1039
  return js_from_native_external
515
1040
  .Call(
@@ -518,61 +1043,6 @@ Napi::Value Reboot_createExternalContext(const Napi::CallbackInfo& info) {
518
1043
  }
519
1044
 
520
1045
 
521
- struct NapiReferenceDeleter {
522
- template <typename T>
523
- void operator()(Napi::Reference<T>* reference) {
524
- adaptor->ScheduleCallbackOnNodeEventLoop(
525
- [reference](Napi::Env) {
526
- delete reference;
527
- });
528
- }
529
- };
530
-
531
-
532
- template <typename T>
533
- class NapiSafeReference {
534
- public:
535
- NapiSafeReference(T t)
536
- : _reference(
537
- new Napi::Reference<T>(Napi::Persistent(std::move(t))),
538
- NapiReferenceDeleter()) {}
539
-
540
- NapiSafeReference(Napi::Reference<T>&& reference)
541
- : _reference(
542
- new Napi::Reference<T>(std::move(reference)),
543
- NapiReferenceDeleter()) {}
544
-
545
- // Helper for getting the value of the reference. We require a
546
- // `Napi::Env` to ensure we only try and get the value from within a
547
- // Node thread (e.g., using the main thread or from within a NAPI
548
- // thread safe function).
549
- T Value(Napi::Env) {
550
- return _reference->Value();
551
- }
552
-
553
- private:
554
- std::shared_ptr<Napi::Reference<T>> _reference;
555
- };
556
-
557
- using NapiSafeFunctionReference = NapiSafeReference<Napi::Function>;
558
- using NapiSafeObjectReference = NapiSafeReference<Napi::Object>;
559
-
560
- // For every servicer we construct a _subclass_ of our Python
561
- // generated Node adaptor, e.g., `GreeterServicerNodeAdaptor`, _at
562
- // runtime_ which we add to the following Python module. This module
563
- // also includes our C++ <-> Python class wrappers for storing NAPI
564
- // objects in Python to properly handle memory management.
565
- PYBIND11_EMBEDDED_MODULE(reboot_native, m) {
566
- py::class_<NapiSafeFunctionReference>(
567
- m,
568
- "NapiSafeFunctionReference");
569
-
570
- py::class_<NapiSafeObjectReference>(
571
- m,
572
- "NapiSafeObjectReference");
573
- }
574
-
575
-
576
1046
  // NOTE: must be called within _Node_.
577
1047
  Napi::Object make_js_context(
578
1048
  Napi::Env& env,
@@ -580,58 +1050,34 @@ Napi::Object make_js_context(
580
1050
  py::object* py_aborted,
581
1051
  const std::string& kind) {
582
1052
  Napi::External<py::object> js_external_context =
583
- Napi::External<py::object>::New(
584
- env,
585
- py_context,
586
- [](Napi::Env, py::object* py_context) {
587
- adaptor->ScheduleCallbackOnPythonEventLoop(
588
- [py_context]() {
589
- delete py_context;
590
- });
591
- });
592
-
593
- auto deferred = std::make_shared<Napi::Promise::Deferred>(env);
594
-
595
- auto promise = deferred->Promise();
596
-
597
- adaptor->ScheduleCallbackOnPythonEventLoop(
598
- [py_aborted, deferred = std::move(deferred)]() {
599
- py_aborted->attr("add_done_callback")(py::cpp_function(
600
- [deferred = std::move(deferred)](
601
- py::object py_future) {
602
- py::object py_exception =
603
- py_future.attr("exception")();
604
-
605
- std::optional<std::string> exception;
606
-
607
- if (!py_exception.is_none()) {
608
- py::str py_exception_string =
609
- py_exception_str(py_exception);
610
-
611
- exception.emplace(py_exception_string);
612
- }
613
-
614
- adaptor->ScheduleCallbackOnNodeEventLoop(
615
- [deferred = std::move(deferred),
616
- exception = std::move(exception)](
617
- Napi::Env env) {
618
- if (!exception.has_value()) {
619
- deferred->Resolve(env.Undefined());
620
- } else {
621
- deferred->Reject(
622
- Napi::Error::New(env, *exception)
623
- .Value());
624
- }
625
- });
626
- }));
1053
+ make_napi_external(env, py_context);
1054
+
1055
+ Napi::Promise js_aborted = NodePromiseFromPythonFuture(
1056
+ env,
1057
+ [py_aborted]() {
1058
+ // After returning we won't need `py_aborted` anymore, so we
1059
+ // make a copy on the stack so we can delete our copy on the
1060
+ // heap.
1061
+ py::object py_aborted_on_stack = *py_aborted;
627
1062
  delete py_aborted;
1063
+ return py_aborted_on_stack;
1064
+ },
1065
+ [](py::object py_result) {
1066
+ // TODO: check that `py_result` is `None`.
1067
+ return std::nullopt;
1068
+ },
1069
+ [](Napi::Env env, std::nullopt_t) {
1070
+ // NOTE: we're explicitly returning `undefined` here instead
1071
+ // of `null` which is the default if we were to not include
1072
+ // this lambda or the one above.
1073
+ return env.Undefined();
628
1074
  });
629
1075
 
630
1076
  return js_Context_fromNativeExternal
631
1077
  ->Call(
632
1078
  {js_external_context,
633
1079
  Napi::String::New(env, kind),
634
- promise})
1080
+ js_aborted})
635
1081
  .As<Napi::Object>();
636
1082
  }
637
1083
 
@@ -661,21 +1107,17 @@ py::object make_py_authorizer(NapiSafeObjectReference js_authorizer) {
661
1107
  py::object py_aborted,
662
1108
  std::optional<std::string> bytes_state,
663
1109
  std::optional<std::string> bytes_request) {
664
- py::object py_future =
665
- py::module::import("asyncio").attr("Future")();
666
-
667
1110
  NapiSafeObjectReference* js_authorizer_reference =
668
1111
  self.attr("_js_authorizer")
669
1112
  .cast<NapiSafeObjectReference*>();
670
1113
 
671
- adaptor->ScheduleCallbackOnNodeEventLoop(
1114
+ return PythonFutureFromNodePromise(
672
1115
  [js_authorizer_reference,
673
1116
  py_reader_context = new py::object(py_reader_context),
674
1117
  py_aborted = new py::object(py_aborted),
675
1118
  method_name,
676
1119
  bytes_state = std::move(bytes_state),
677
- bytes_request = std::move(bytes_request),
678
- py_future = new py::object(py_future)](Napi::Env env) {
1120
+ bytes_request = std::move(bytes_request)](Napi::Env env) {
679
1121
  std::vector<Napi::Value> js_args;
680
1122
 
681
1123
  Napi::Object js_context =
@@ -695,63 +1137,18 @@ py::object make_py_authorizer(NapiSafeObjectReference js_authorizer) {
695
1137
  js_args.push_back(js_bytes_state);
696
1138
  js_args.push_back(js_bytes_request);
697
1139
 
698
- Napi::Object js_promise =
699
- js_authorizer.Get("_authorize")
700
- .As<Napi::Function>()
701
- .Call(js_authorizer, {js_args})
702
- .As<Napi::Object>();
703
-
704
- js_promise
705
- .Get("then")
1140
+ return js_authorizer.Get("_authorize")
706
1141
  .As<Napi::Function>()
707
- .Call(
708
- js_promise,
709
- {Napi::Function::New(
710
- env,
711
- [py_future](
712
- const Napi::CallbackInfo& info) {
713
- Napi::Uint8Array js_bytes_decision =
714
- info[0].As<Napi::Uint8Array>();
715
-
716
- std::string bytes_decision = uint8array_to_str(
717
- js_bytes_decision);
718
-
719
- adaptor->ScheduleCallbackOnPythonEventLoop(
720
- [py_future, bytes_decision]() {
721
- bool cancelled =
722
- py_future->attr("cancelled")()
723
- .cast<bool>();
724
- if (!cancelled) {
725
- py_future->attr("set_result")(
726
- py::bytes(bytes_decision));
727
- }
728
- delete py_future;
729
- });
730
- }),
731
- Napi::Function::New(
732
- env,
733
- [py_future](const Napi::CallbackInfo& info) {
734
- std::string message = message_from_js_error(
735
- info[0].As<Napi::Object>());
736
-
737
- adaptor->ScheduleCallbackOnPythonEventLoop(
738
- [py_future,
739
- message = std::move(message)]() {
740
- bool cancelled =
741
- py_future->attr("cancelled")()
742
- .cast<bool>();
743
- if (!cancelled) {
744
- py_future->attr("set_exception")(
745
- py::module::import("builtins")
746
- .attr("Exception")(
747
- message));
748
- }
749
- delete py_future;
750
- });
751
- })});
1142
+ .Call(js_authorizer, {js_args})
1143
+ .As<Napi::Object>();
1144
+ },
1145
+ [](Napi::Env env, Napi::Value js_bytes_decision) {
1146
+ return uint8array_to_str(
1147
+ js_bytes_decision.As<Napi::Uint8Array>());
1148
+ },
1149
+ [](std::string&& bytes_decision) {
1150
+ return py::bytes(bytes_decision);
752
1151
  });
753
-
754
- return py_future;
755
1152
  },
756
1153
  py::name("_authorize"),
757
1154
  py::arg("method_name"),
@@ -795,7 +1192,7 @@ using ServicerDetails =
795
1192
 
796
1193
  // NOTE: must be called within _Node_.
797
1194
  std::vector<std::shared_ptr<ServicerDetails>> make_servicer_details(
798
- Napi::Env& env,
1195
+ Napi::Env env,
799
1196
  const Napi::Array& js_servicers) {
800
1197
  std::vector<std::shared_ptr<ServicerDetails>> servicer_details;
801
1198
 
@@ -887,13 +1284,10 @@ py::object make_py_user_servicer(
887
1284
  py_self.attr("_js_servicer_constructor")
888
1285
  .cast<NapiSafeFunctionReference*>();
889
1286
 
890
- std::promise<NapiSafeObjectReference> promise;
891
-
892
- adaptor->ScheduleCallbackOnNodeEventLoop(
893
- [&promise,
894
- py_self = new py::object(py_self),
895
- js_servicer_constructor](Napi::Env env) {
896
- try {
1287
+ try {
1288
+ return RunCallbackOnNodeEventLoop(
1289
+ [py_self = new py::object(py_self),
1290
+ js_servicer_constructor](Napi::Env env) {
897
1291
  Napi::Object js_servicer =
898
1292
  js_servicer_constructor->Value(env).New({});
899
1293
 
@@ -901,35 +1295,16 @@ py::object make_py_user_servicer(
901
1295
  // external so that we can call `read()` and `write()`
902
1296
  // on it.
903
1297
  Napi::External<py::object> js_external_self =
904
- Napi::External<py::object>::New(
905
- env,
906
- py_self,
907
- [](Napi::Env, py::object* py_self) {
908
- adaptor->ScheduleCallbackOnPythonEventLoop(
909
- [py_self]() {
910
- delete py_self;
911
- });
912
- });
1298
+ make_napi_external(env, py_self);
913
1299
 
914
1300
  js_servicer
915
1301
  .Get("__storeExternal")
916
1302
  .As<Napi::Function>()
917
1303
  .Call(js_servicer, {js_external_self});
918
1304
 
919
- promise.set_value(NapiSafeObjectReference(js_servicer));
920
- } catch (const Napi::Error& e) {
921
- env.Global()
922
- .Get("console")
923
- .As<Napi::Object>()
924
- .Get("log")
925
- .As<Napi::Function>()
926
- .Call({e.Value(), Napi::String::New(env, "\n")});
927
- promise.set_exception(
928
- std::make_exception_ptr(std::runtime_error(e.what())));
929
- }
930
- });
931
- try {
932
- return promise.get_future().get();
1305
+ return NapiSafeObjectReference(js_servicer);
1306
+ },
1307
+ /* warn = */ true);
933
1308
  } catch (const std::exception& e) {
934
1309
  PyErr_SetString(
935
1310
  py::module::import("reboot.aio.servers")
@@ -951,18 +1326,14 @@ py::object make_py_user_servicer(
951
1326
  py::object py_aborted,
952
1327
  const std::string& json_state,
953
1328
  const std::string& json_request) {
954
- py::object py_future =
955
- py::module::import("asyncio").attr("Future")();
956
-
957
- adaptor->ScheduleCallbackOnNodeEventLoop(
1329
+ return PythonFutureFromNodePromise(
958
1330
  [&js_servicer_reference,
959
1331
  kind,
960
1332
  method,
961
1333
  py_context = new py::object(py_context),
962
1334
  py_aborted = new py::object(py_aborted),
963
1335
  json_state,
964
- json_request,
965
- py_future = new py::object(py_future)](Napi::Env env) {
1336
+ json_request](Napi::Env env) {
966
1337
  std::vector<Napi::Value> js_args;
967
1338
 
968
1339
  Napi::Object js_context =
@@ -976,94 +1347,37 @@ py::object make_py_user_servicer(
976
1347
  Napi::Object js_servicer =
977
1348
  js_servicer_reference.Value(env);
978
1349
 
979
- Napi::Object js_promise =
980
- js_servicer
981
- .Get("_" + method)
982
- .As<Napi::Function>()
983
- .Call(js_servicer, js_args)
984
- .As<Napi::Object>();
985
-
986
- js_promise
987
- .Get("then")
1350
+ return js_servicer
1351
+ .Get("_" + method)
988
1352
  .As<Napi::Function>()
989
- .Call(
990
- js_promise,
991
- {Napi::Function::New(
992
- env,
993
- [py_future](const Napi::CallbackInfo& info) {
994
- std::string json =
995
- info[0].As<Napi::String>().Utf8Value();
996
-
997
- adaptor->ScheduleCallbackOnPythonEventLoop(
998
- [py_future, json]() {
999
- bool cancelled =
1000
- py_future->attr("cancelled")()
1001
- .cast<bool>();
1002
- if (!cancelled) {
1003
- py_future->attr("set_result")(json);
1004
- }
1005
- delete py_future;
1006
- });
1007
- }),
1008
- Napi::Function::New(
1009
- env,
1010
- [py_future](const Napi::CallbackInfo& info) {
1011
- std::string message = message_from_js_error(
1012
- info[0].As<Napi::Object>());
1013
-
1014
- adaptor->ScheduleCallbackOnPythonEventLoop(
1015
- [py_future,
1016
- message = std::move(message)]() {
1017
- bool cancelled =
1018
- py_future->attr("cancelled")()
1019
- .cast<bool>();
1020
- if (!cancelled) {
1021
- py_future->attr("set_exception")(
1022
- py::module::import("builtins")
1023
- .attr("Exception")(
1024
- message));
1025
- }
1026
- delete py_future;
1027
- });
1028
- })});
1353
+ .Call(js_servicer, js_args)
1354
+ .As<Napi::Object>();
1355
+ },
1356
+ [](Napi::Env env, Napi::Value value) {
1357
+ return std::string(value.As<Napi::String>().Utf8Value());
1029
1358
  });
1030
-
1031
- return py_future;
1032
1359
  });
1033
1360
 
1034
1361
  // Constructs and returns an Authorizer.
1035
1362
  attributes["_construct_authorizer"] = py::cpp_function(
1036
1363
  [](NapiSafeObjectReference& js_servicer_reference) -> py::object {
1037
- std::promise<std::optional<NapiSafeObjectReference>> promise;
1038
- adaptor->ScheduleCallbackOnNodeEventLoop(
1039
- [&js_servicer_reference, &promise](Napi::Env env) {
1040
- try {
1364
+ std::optional<NapiSafeObjectReference> js_authorizer;
1365
+ try {
1366
+ js_authorizer = RunCallbackOnNodeEventLoop(
1367
+ [&js_servicer_reference](Napi::Env env)
1368
+ -> std::optional<NapiSafeObjectReference> {
1041
1369
  Napi::Object js_servicer = js_servicer_reference.Value(env);
1042
1370
  Napi::Value js_authorizer =
1043
1371
  js_servicer.Get("_authorizer")
1044
1372
  .As<Napi::Function>()
1045
1373
  .Call(js_servicer, {});
1046
1374
  if (!js_authorizer.IsNull()) {
1047
- promise.set_value(
1048
- NapiSafeObjectReference(
1049
- js_authorizer.As<Napi::Object>()));
1375
+ return NapiSafeObjectReference(
1376
+ js_authorizer.As<Napi::Object>());
1050
1377
  } else {
1051
- promise.set_value(std::nullopt);
1378
+ return std::nullopt;
1052
1379
  }
1053
- } catch (const Napi::Error& e) {
1054
- env.Global()
1055
- .Get("console")
1056
- .As<Napi::Object>()
1057
- .Get("log")
1058
- .As<Napi::Function>()
1059
- .Call({e.Value(), Napi::String::New(env, "\n")});
1060
- promise.set_exception(
1061
- std::make_exception_ptr(std::runtime_error(e.what())));
1062
- }
1063
- });
1064
- std::optional<NapiSafeObjectReference> js_authorizer;
1065
- try {
1066
- js_authorizer = promise.get_future().get();
1380
+ });
1067
1381
  } catch (const std::exception& e) {
1068
1382
  PyErr_SetString(
1069
1383
  py::module::import("reboot.aio.servers")
@@ -1156,20 +1470,28 @@ py::object make_py_token_verifier(NapiSafeObjectReference js_token_verifier) {
1156
1470
  [](py::object self,
1157
1471
  py::object py_reader_context,
1158
1472
  py::object py_aborted,
1159
- const std::string& token) {
1160
- py::object py_future =
1161
- py::module::import("asyncio").attr("Future")();
1162
-
1473
+ py::object py_token) {
1163
1474
  NapiSafeObjectReference* js_token_verifier_reference =
1164
1475
  self.attr("_js_token_verifier")
1165
1476
  .cast<NapiSafeObjectReference*>();
1166
1477
 
1167
- adaptor->ScheduleCallbackOnNodeEventLoop(
1478
+ // Converting to 'py::str' involves Python runtime access.
1479
+ // Perform this conversion here, on the Python thread, before entering
1480
+ // Node.
1481
+ std::optional<std::string> token;
1482
+ if (!py_token.is_none()) {
1483
+ token = py::str(py_token);
1484
+ }
1485
+
1486
+ return PythonFutureFromNodePromise(
1168
1487
  [js_token_verifier_reference,
1488
+ // We allocate 'py_aborted' and 'py_reader_context' with 'new
1489
+ // py::object' on the Python thread, ensuring they remain valid
1490
+ // when passed into Node. Cleanup is handled later on the Python
1491
+ // thread, see 'make_js_context'.
1169
1492
  py_reader_context = new py::object(py_reader_context),
1170
1493
  py_aborted = new py::object(py_aborted),
1171
- token,
1172
- py_future = new py::object(py_future)](Napi::Env env) {
1494
+ token = std::move(token)](Napi::Env env) {
1173
1495
  std::vector<Napi::Value> js_args;
1174
1496
 
1175
1497
  js_args.push_back(
@@ -1179,85 +1501,49 @@ py::object make_py_token_verifier(NapiSafeObjectReference js_token_verifier) {
1179
1501
  py_aborted,
1180
1502
  "reader"));
1181
1503
 
1182
- js_args.push_back(Napi::String::New(env, token));
1504
+ if (token.has_value()) {
1505
+ js_args.push_back(Napi::String::New(env, *token));
1506
+ } else {
1507
+ js_args.push_back(env.Undefined());
1508
+ }
1183
1509
 
1184
1510
  Napi::Object js_token_verifier =
1185
1511
  js_token_verifier_reference->Value(env);
1186
- Napi::Object js_promise =
1187
- js_token_verifier
1188
- .Get("_verifyToken")
1189
- .As<Napi::Function>()
1190
- .Call(js_token_verifier, {js_args})
1191
- .As<Napi::Object>();
1192
-
1193
- js_promise
1194
- .Get("then")
1512
+
1513
+ return js_token_verifier
1514
+ .Get("_verifyToken")
1195
1515
  .As<Napi::Function>()
1196
- .Call(
1197
- js_promise,
1198
- {Napi::Function::New(
1199
- env,
1200
- [py_future](const Napi::CallbackInfo& info) {
1201
- std::optional<std::string> bytes_auth;
1202
- if (!info[0].IsNull()) {
1203
- bytes_auth =
1204
- uint8array_to_str(
1205
- info[0].As<Napi::Uint8Array>());
1206
- }
1207
-
1208
- adaptor->ScheduleCallbackOnPythonEventLoop(
1209
- [py_future, bytes_auth]() {
1210
- bool cancelled =
1211
- py_future->attr("cancelled")()
1212
- .cast<bool>();
1213
- if (!cancelled) {
1214
- if (bytes_auth.has_value()) {
1215
- py_future->attr("set_result")(
1216
- py::bytes(*bytes_auth));
1217
- } else {
1218
- py_future->attr("set_result")(
1219
- py::none());
1220
- }
1221
- }
1222
- delete py_future;
1223
- });
1224
- }),
1225
- Napi::Function::New(
1226
- env,
1227
- [py_future](const Napi::CallbackInfo& info) {
1228
- std::string message = message_from_js_error(
1229
- info[0].As<Napi::Object>());
1230
-
1231
- adaptor->ScheduleCallbackOnPythonEventLoop(
1232
- [py_future,
1233
- message = std::move(message)]() {
1234
- bool cancelled =
1235
- py_future->attr("cancelled")()
1236
- .cast<bool>();
1237
- if (!cancelled) {
1238
- py_future->attr("set_exception")(
1239
- py::module::import("builtins")
1240
- .attr("Exception")(
1241
- message));
1242
- }
1243
- delete py_future;
1244
- });
1245
- })});
1516
+ .Call(js_token_verifier, {js_args})
1517
+ .As<Napi::Object>();
1518
+ },
1519
+ [](Napi::Env env, Napi::Value value) {
1520
+ std::optional<std::string> bytes_auth;
1521
+ if (!value.IsNull()) {
1522
+ bytes_auth = uint8array_to_str(value.As<Napi::Uint8Array>());
1523
+ }
1524
+ return bytes_auth;
1525
+ },
1526
+ [](std::optional<std::string>&& bytes_auth) -> py::object {
1527
+ if (bytes_auth.has_value()) {
1528
+ return py::bytes(*bytes_auth);
1529
+ } else {
1530
+ return py::none();
1531
+ }
1246
1532
  });
1247
-
1248
- return py_future;
1249
1533
  },
1250
1534
  py::name("_verify_token"),
1251
1535
  py::arg("context"),
1252
1536
  py::arg("aborted"),
1253
- py::arg("token"),
1537
+ py::arg("py_token"),
1254
1538
  py::is_method(py::none()));
1255
1539
 
1256
1540
  py::object py_parent_class =
1257
1541
  py::module::import("reboot.nodejs.python")
1258
1542
  .attr("NodeAdaptorTokenVerifier");
1543
+
1259
1544
  py::object py_parent_metaclass = py::reinterpret_borrow<py::object>(
1260
1545
  (PyObject*) &PyType_Type);
1546
+
1261
1547
  py::object py_token_verifier = py_parent_metaclass(
1262
1548
  "_NodeAdaptorTokenVerifier",
1263
1549
  py::make_tuple(py_parent_class),
@@ -1278,94 +1564,45 @@ Napi::Value Reboot_up(const Napi::CallbackInfo& info) {
1278
1564
 
1279
1565
  py::object* py_reboot = js_external_reboot.Value(info.Env()).Data();
1280
1566
 
1281
- Napi::Array js_servicers = info[1].As<Napi::Array>();
1567
+ auto js_external_application = NapiSafeReference(
1568
+ info[1].As<Napi::External<py::object>>());
1282
1569
 
1283
- std::optional<NapiSafeObjectReference> js_token_verifier;
1284
- if (info[2].IsObject()) {
1285
- js_token_verifier = NapiSafeReference(info[2].As<Napi::Object>());
1286
- }
1570
+ py::object* py_application = js_external_application.Value(info.Env()).Data();
1287
1571
 
1288
1572
  bool local_envoy = false;
1289
- if (!info[3].IsUndefined()) {
1290
- local_envoy = info[3].As<Napi::Boolean>();
1573
+ if (!info[2].IsUndefined()) {
1574
+ local_envoy = info[2].As<Napi::Boolean>();
1291
1575
  }
1292
1576
 
1293
- auto env = info.Env();
1294
- auto servicer_details = make_servicer_details(env, js_servicers);
1295
-
1296
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
1297
-
1298
- auto promise = deferred->Promise();
1299
-
1300
- adaptor->ScheduleCallbackOnPythonEventLoop(
1577
+ return NodePromiseFromPythonTask(
1578
+ info.Env(),
1579
+ "Reboot.up(...) in nodejs",
1580
+ {"asyncio", "create_task"},
1301
1581
  [js_external_reboot, // Ensures `py_reboot` remains valid.
1302
1582
  py_reboot,
1303
- js_token_verifier,
1304
- local_envoy,
1305
- servicer_details = std::move(servicer_details),
1306
- deferred = std::move(deferred)]() {
1307
- py::list py_servicers = make_py_servicers(servicer_details);
1308
- py::object py_token_verifier = py::none();
1309
- if (js_token_verifier.has_value()) {
1310
- py_token_verifier = make_py_token_verifier(*js_token_verifier);
1311
- }
1312
-
1313
- py::object py_task =
1314
- py::module::import("asyncio").attr("create_task")(
1315
- py_reboot->attr("up")(
1316
- "servicers"_a = py_servicers,
1317
- "token_verifier"_a = py_token_verifier,
1318
- // NOTE: while we support subprocess consensuses
1319
- // for `rbt dev` and `rbt serve` we do not support
1320
- // them for tests because we don't have a way to
1321
- // clone a process like we do with multiprocessing
1322
- // in Python.
1323
- "in_process"_a = true,
1324
- "local_envoy"_a = local_envoy),
1325
- "name"_a = "Reboot.up(...) in nodejs");
1326
-
1327
- py_task.attr("add_done_callback")(py::cpp_function(
1328
- [deferred = std::move(deferred),
1329
- // NOTE: need to keep a reference to `py_task` so that it
1330
- // doesn't get destroyed before completing!
1331
- py_task = new py::object(py_task)](py::object py_future) {
1332
- py::object py_exception = py_future.attr("exception")();
1333
-
1334
- std::optional<std::string> application_id;
1335
- std::optional<std::string> exception;
1336
-
1337
- if (py_exception.is_none()) {
1338
- py::str py_application_id =
1339
- py_future.attr("result")().attr("application_id")();
1340
- application_id.emplace(py_application_id);
1341
- } else {
1342
- py::str py_exception_string = py_exception_str(py_exception);
1343
-
1344
- exception.emplace(py_exception_string);
1345
- }
1346
-
1347
- adaptor->ScheduleCallbackOnNodeEventLoop(
1348
- [deferred = std::move(deferred),
1349
- application_id = std::move(application_id),
1350
- exception = std::move(exception)](Napi::Env env) {
1351
- if (application_id.has_value()) {
1352
- auto js_application_config = Napi::Object::New(env);
1353
- js_application_config.Set(
1354
- "applicationId",
1355
- *application_id);
1356
- deferred->Resolve(js_application_config);
1357
- } else {
1358
- deferred->Reject(
1359
- Napi::Error::New(env, *exception).Value());
1360
- }
1361
- });
1362
-
1363
- delete py_task;
1364
- },
1365
- py::arg("future")));
1583
+ js_external_application, // Ensures `py_application` remains valid.
1584
+ py_application,
1585
+ local_envoy]() {
1586
+ return py_reboot->attr("up")(
1587
+ py_application,
1588
+ // NOTE: while we support subprocess consensuses
1589
+ // for `rbt dev` and `rbt serve` we do not support
1590
+ // them for tests because we don't have a way to
1591
+ // clone a process like we do with multiprocessing
1592
+ // in Python.
1593
+ "in_process"_a = true,
1594
+ "local_envoy"_a = local_envoy);
1595
+ },
1596
+ [](py::object py_revision) {
1597
+ py::str py_application_id =
1598
+ py_revision.attr("config").attr("application_id")();
1599
+ return std::string(py_application_id);
1600
+ },
1601
+ [](Napi::Env env, std::string&& application_id) {
1602
+ Napi::Object js_revision = Napi::Object::New(env);
1603
+ js_revision.Set("applicationId", application_id);
1604
+ return js_revision;
1366
1605
  });
1367
-
1368
- return promise;
1369
1606
  }
1370
1607
 
1371
1608
  void Reboot_down(const Napi::CallbackInfo& info) {
@@ -1392,49 +1629,13 @@ Napi::Value Reboot_start(const Napi::CallbackInfo& info) {
1392
1629
  // flag, rather than incrementing and decrementing a reference count.
1393
1630
  adaptor->thread_safe_function.Ref(info.Env());
1394
1631
 
1395
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
1396
-
1397
- auto promise = deferred->Promise();
1398
-
1399
- adaptor->ScheduleCallbackOnPythonEventLoop(
1400
- [py_reboot, deferred = std::move(deferred)]() {
1401
- py::object py_task =
1402
- py::module::import("asyncio").attr("create_task")(
1403
- py_reboot->attr("start")(),
1404
- "name"_a = "Reboot.start() in nodejs");
1405
-
1406
- py_task.attr("add_done_callback")(py::cpp_function(
1407
- [deferred = std::move(deferred),
1408
- // NOTE: need to keep a reference to `py_task` so that it
1409
- // doesn't get destroyed before completing!
1410
- py_task = new py::object(py_task)](py::object py_future) {
1411
- py::object py_exception = py_future.attr("exception")();
1412
-
1413
- std::optional<std::string> exception;
1414
-
1415
- if (!py_exception.is_none()) {
1416
- py::str py_exception_string = py_exception_str(py_exception);
1417
-
1418
- exception.emplace(py_exception_string);
1419
- }
1420
-
1421
- adaptor->ScheduleCallbackOnNodeEventLoop(
1422
- [deferred = std::move(deferred),
1423
- exception = std::move(exception)](Napi::Env env) {
1424
- if (!exception.has_value()) {
1425
- deferred->Resolve(env.Null());
1426
- } else {
1427
- deferred->Reject(
1428
- Napi::Error::New(env, *exception).Value());
1429
- }
1430
- });
1431
-
1432
- delete py_task;
1433
- },
1434
- py::arg("future")));
1632
+ return NodePromiseFromPythonTask(
1633
+ info.Env(),
1634
+ "Reboot.start() in nodejs",
1635
+ {"asyncio", "create_task"},
1636
+ [py_reboot]() {
1637
+ return py_reboot->attr("start")();
1435
1638
  });
1436
-
1437
- return promise;
1438
1639
  }
1439
1640
 
1440
1641
  Napi::Value Reboot_stop(const Napi::CallbackInfo& info) {
@@ -1448,58 +1649,34 @@ Napi::Value Reboot_stop(const Napi::CallbackInfo& info) {
1448
1649
 
1449
1650
  py::object* py_reboot = js_external_reboot.Value(info.Env()).Data();
1450
1651
 
1451
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
1452
-
1453
- auto promise = deferred->Promise();
1454
-
1455
- adaptor->ScheduleCallbackOnPythonEventLoop(
1456
- [py_reboot, deferred = std::move(deferred)]() {
1457
- py::object py_task =
1458
- py::module::import("asyncio").attr("create_task")(
1459
- py_reboot->attr("stop")(),
1460
- "name"_a = "Reboot.stop() in nodejs");
1461
-
1462
- py_task.attr("add_done_callback")(py::cpp_function(
1463
- [deferred = std::move(deferred),
1464
- // NOTE: need to keep a reference to `py_task` so that it
1465
- // doesn't get destroyed before completing!
1466
- py_task = new py::object(py_task)](py::object py_future) {
1467
- py::object py_exception = py_future.attr("exception")();
1468
-
1469
- std::optional<std::string> exception;
1470
-
1471
- if (!py_exception.is_none()) {
1472
- py::str py_exception_string = py_exception_str(py_exception);
1473
-
1474
- exception.emplace(py_exception_string);
1475
- }
1476
-
1477
- adaptor->ScheduleCallbackOnNodeEventLoop(
1478
- [deferred = std::move(deferred),
1479
- exception = std::move(exception)](Napi::Env env) {
1480
- if (!exception.has_value()) {
1481
- deferred->Resolve(env.Null());
1482
- } else {
1483
- deferred->Reject(
1484
- Napi::Error::New(env, *exception).Value());
1485
- }
1486
- // Call Unref() on our NAPI thread safe function so that the
1487
- // object can be destroyed and the event loop can exit.
1488
- // See:
1489
- // (check_line_length skip)
1490
- // https://nodejs.github.io/node-addon-examples/special-topics/thread-safe-functions/#q-my-application-isnt-exiting-correctly-it-just-hangs.
1491
- // Note that Ref() and Unref() will set and unset an
1492
- // "is_referenced" boolean flag, rather than incrementing
1493
- // and decrementing a reference count.
1494
- adaptor->thread_safe_function.Unref(env);
1495
- });
1652
+ Napi::Promise js_promise = NodePromiseFromPythonTask(
1653
+ info.Env(),
1654
+ "Reboot.stop() in nodejs",
1655
+ {"asyncio", "create_task"},
1656
+ [py_reboot]() {
1657
+ return py_reboot->attr("stop")();
1658
+ });
1496
1659
 
1497
- delete py_task;
1498
- },
1499
- py::arg("future")));
1660
+ // Call Unref() on our NAPI thread safe function so that the
1661
+ // object can be destroyed and the event loop can exit.
1662
+ // See:
1663
+ // (check_line_length skip)
1664
+ // https://nodejs.github.io/node-addon-examples/special-topics/thread-safe-functions/#q-my-application-isnt-exiting-correctly-it-just-hangs.
1665
+ // Note that Ref() and Unref() will set and unset an
1666
+ // "is_referenced" boolean flag, rather than incrementing
1667
+ // and decrementing a reference count.
1668
+ auto js_resolve_reject = Napi::Function::New(
1669
+ info.Env(),
1670
+ [](const Napi::CallbackInfo& info) {
1671
+ adaptor->thread_safe_function.Unref(info.Env());
1500
1672
  });
1501
1673
 
1502
- return promise;
1674
+ js_promise
1675
+ .Get("then")
1676
+ .As<Napi::Function>()
1677
+ .Call(js_promise, {js_resolve_reject, js_resolve_reject});
1678
+
1679
+ return js_promise;
1503
1680
  }
1504
1681
 
1505
1682
  // NOTE: We block on a promise here, so this method should not be called outside
@@ -1515,15 +1692,13 @@ Napi::Value Reboot_url(const Napi::CallbackInfo& info) {
1515
1692
 
1516
1693
  py::object* py_reboot = js_external_reboot.Value(info.Env()).Data();
1517
1694
 
1518
- std::promise<std::string> promise;
1519
-
1520
- adaptor->ScheduleCallbackOnPythonEventLoop(
1521
- [py_reboot, &promise]() {
1695
+ std::string url = RunCallbackOnPythonEventLoop(
1696
+ [py_reboot]() {
1522
1697
  py::str url = py_reboot->attr("url")();
1523
- promise.set_value(std::string(url));
1698
+ return std::string(url);
1524
1699
  });
1525
1700
 
1526
- return Napi::String::New(info.Env(), promise.get_future().get());
1701
+ return Napi::String::New(info.Env(), url);
1527
1702
  }
1528
1703
 
1529
1704
  Napi::Value Service_constructor(const Napi::CallbackInfo& info) {
@@ -1537,35 +1712,22 @@ Napi::Value Service_constructor(const Napi::CallbackInfo& info) {
1537
1712
 
1538
1713
  std::string id = js_args.Get("id").As<Napi::String>().Utf8Value();
1539
1714
 
1540
- std::promise<py::object*> promise;
1541
-
1542
- adaptor->ScheduleCallbackOnPythonEventLoop(
1543
- [&rbt_module, &node_adaptor, &id, &promise]() {
1715
+ py::object* py_service = RunCallbackOnPythonEventLoop(
1716
+ [&rbt_module, &node_adaptor, &id]() {
1544
1717
  py::object py_module = py::module::import(rbt_module.c_str());
1545
1718
  py::object py_schedule_type =
1546
1719
  py_module
1547
1720
  .attr(node_adaptor.c_str())
1548
1721
  .attr("_Schedule");
1549
- promise.set_value(
1550
- new py::object(py_module.attr(node_adaptor.c_str())(
1551
- // The call will stay within the same application.
1552
- "application_id"_a = py::none(),
1553
- "state_id"_a = id,
1554
- "schedule_type"_a = py_schedule_type)));
1722
+ return new py::object(py_module.attr(node_adaptor.c_str())(
1723
+ // The call will stay within the same application.
1724
+ "application_id"_a = py::none(),
1725
+ "state_id"_a = id,
1726
+ "schedule_type"_a = py_schedule_type));
1555
1727
  });
1556
1728
 
1557
- py::object* py_service = promise.get_future().get();
1558
-
1559
1729
  Napi::External<py::object> js_external_service =
1560
- Napi::External<py::object>::New(
1561
- info.Env(),
1562
- py_service,
1563
- [](Napi::Env, py::object* py_service) {
1564
- adaptor->ScheduleCallbackOnPythonEventLoop(
1565
- [py_service]() {
1566
- delete py_service;
1567
- });
1568
- });
1730
+ make_napi_external(info.Env(), py_service);
1569
1731
 
1570
1732
  // auto type_tag = MakeTypeTag(module, type);
1571
1733
  // js_external_service.TypeTag(&type_tag);
@@ -1607,23 +1769,20 @@ Napi::Value Service_call(const Napi::CallbackInfo& info) {
1607
1769
 
1608
1770
  std::string json_options = js_args.Get("jsonOptions").As<Napi::String>();
1609
1771
 
1610
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
1611
-
1612
- auto promise = deferred->Promise();
1613
-
1614
- adaptor->ScheduleCallbackOnPythonEventLoop(
1772
+ return NodePromiseFromPythonTaskWithContext(
1773
+ info.Env(),
1774
+ "servicer._" + kind + "(\"" + method + "\", ...) in nodejs",
1775
+ js_external_context,
1615
1776
  [js_external_service, // Ensures `py_service` remains valid.
1616
1777
  py_service,
1617
1778
  kind,
1618
1779
  method,
1619
1780
  request_module,
1620
1781
  request_type,
1621
- // Ensures `py_context` remains valid.
1622
- js_external_context,
1782
+ js_external_context, // Ensures `py_context` remains valid.
1623
1783
  py_context,
1624
1784
  json_request,
1625
- json_options,
1626
- deferred = std::move(deferred)]() {
1785
+ json_options]() {
1627
1786
  // If a type is a nested message type, we can't import it directly
1628
1787
  // as 'from module import Foo.Bar.Baz', instead we need to import the
1629
1788
  // top level 'Foo' and call 'Foo.Bar.Baz' on it.
@@ -1640,73 +1799,25 @@ Napi::Value Service_call(const Napi::CallbackInfo& info) {
1640
1799
  py_request_type = py_request_type.attr(part.c_str());
1641
1800
  }
1642
1801
 
1643
- py::object py_task =
1644
- py::module::import("reboot.nodejs.python")
1645
- .attr("create_task_with_context")(
1646
- py_service->attr(("_" + kind).c_str())(
1647
- method,
1648
- py_context,
1649
- py_request_type,
1650
- json_request,
1651
- json_options),
1652
- py_context,
1653
- "name"_a = ("servicer._" + kind
1654
- + "(\"" + method + "\", ...) in nodejs")
1655
- .c_str());
1656
-
1657
- py_task.attr("add_done_callback")(py::cpp_function(
1658
- [deferred = std::move(deferred),
1659
- // NOTE: need to keep a reference to `py_task` so that it
1660
- // doesn't get destroyed before completing!
1661
- py_task = new py::object(py_task)](py::object py_future) {
1662
- py::object py_exception = py_future.attr("exception")();
1663
-
1664
- std::optional<std::string> json;
1665
- std::optional<std::string> exception;
1666
-
1667
- if (py_exception.is_none()) {
1668
- py::str py_json = py_future.attr("result")();
1669
- json.emplace(py_json);
1670
- } else {
1671
- py::str py_exception_string = py_exception_str(py_exception);
1672
-
1673
- exception.emplace(py_exception_string);
1674
- }
1675
-
1676
- adaptor->ScheduleCallbackOnNodeEventLoop(
1677
- [deferred = std::move(deferred),
1678
- json = std::move(json),
1679
- exception = std::move(exception)](Napi::Env env) {
1680
- if (json.has_value()) {
1681
- deferred->Resolve(
1682
- Napi::String::New(env, *json));
1683
- } else {
1684
- deferred->Reject(
1685
- Napi::Error::New(env, *exception).Value());
1686
- }
1687
- });
1688
-
1689
- delete py_task;
1690
- }));
1802
+ return py_service->attr(("_" + kind).c_str())(
1803
+ method,
1804
+ py_context,
1805
+ py_request_type,
1806
+ json_request,
1807
+ json_options);
1808
+ },
1809
+ [](py::object py_json) {
1810
+ return py_json.cast<std::string>();
1811
+ },
1812
+ [](Napi::Env env, std::string&& json) {
1813
+ return Napi::String::New(env, json);
1691
1814
  });
1692
-
1693
- return promise;
1694
1815
  }
1695
1816
 
1696
1817
 
1697
- Napi::Value Future_await(const Napi::CallbackInfo& info) {
1818
+ Napi::Value Task_await(const Napi::CallbackInfo& info) {
1698
1819
  Napi::Object js_args = info[0].As<Napi::Object>();
1699
1820
 
1700
- // NOTE: we immediately get a safe reference to the `Napi::External`
1701
- // so that Node will not garbage collect it and the `py::object*` we
1702
- // get out of it will remain valid.
1703
- auto js_external_service = NapiSafeReference(
1704
- js_args.Get("external").As<Napi::External<py::object>>());
1705
-
1706
- // CHECK(js_external_service.CheckTypeTag(...));
1707
-
1708
- py::object* py_service = js_external_service.Value(info.Env()).Data();
1709
-
1710
1821
  // NOTE: we immediately get a safe reference to the `Napi::External`
1711
1822
  // so that Node will not garbage collect it and the `py::object*` we
1712
1823
  // get out of it will remain valid.
@@ -1718,71 +1829,40 @@ Napi::Value Future_await(const Napi::CallbackInfo& info) {
1718
1829
  py::object* py_context =
1719
1830
  js_external_context.Value(info.Env()).Data();
1720
1831
 
1721
- std::string method = js_args.Get("method").As<Napi::String>();
1832
+ std::string rbt_module = js_args.Get("rbtModule").As<Napi::String>();
1722
1833
 
1723
- std::string json_task_id = js_args.Get("jsonTaskId").As<Napi::String>();
1834
+ std::string state_name = js_args.Get("stateName").As<Napi::String>();
1724
1835
 
1725
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
1836
+ std::string method = js_args.Get("method").As<Napi::String>();
1726
1837
 
1727
- auto promise = deferred->Promise();
1838
+ std::string json_task_id = js_args.Get("jsonTaskId").As<Napi::String>();
1728
1839
 
1729
- adaptor->ScheduleCallbackOnPythonEventLoop(
1730
- [js_external_service, // Ensures `py_service` remains valid.
1731
- py_service,
1732
- method,
1733
- // Ensures `py_context` remains valid.
1734
- js_external_context,
1840
+ return NodePromiseFromPythonTaskWithContext(
1841
+ info.Env(),
1842
+ "reboot.nodejs.python.task_await(\""
1843
+ + state_name + "\", \""
1844
+ + method + "\", ...) in nodejs",
1845
+ js_external_context,
1846
+ [rbt_module = std::move(rbt_module),
1847
+ state_name = std::move(state_name),
1848
+ method = std::move(method),
1849
+ js_external_context, // Ensures `py_context` remains valid.
1735
1850
  py_context,
1736
- json_task_id,
1737
- deferred = std::move(deferred)]() {
1738
- py::object py_task =
1739
- py::module::import("reboot.nodejs.python")
1740
- .attr("create_task_with_context")(
1741
- py_service->attr("_future_await")(
1742
- method,
1743
- py_context,
1744
- json_task_id),
1745
- py_context,
1746
- "name"_a = ("servicer._future_await(\""
1747
- + method + "\", ...) in nodejs")
1748
- .c_str());
1749
- py_task.attr("add_done_callback")(py::cpp_function(
1750
- [deferred = std::move(deferred),
1751
- // NOTE: need to keep a reference to `py_task` so that it
1752
- // doesn't get destroyed before completing!
1753
- py_task = new py::object(py_task)](py::object py_future) {
1754
- py::object py_exception = py_future.attr("exception")();
1755
-
1756
- std::optional<std::string> json;
1757
- std::optional<std::string> exception;
1758
-
1759
- if (py_exception.is_none()) {
1760
- py::str py_json = py_future.attr("result")();
1761
- json.emplace(py_json);
1762
- } else {
1763
- py::str py_exception_string = py_exception_str(py_exception);
1764
-
1765
- exception.emplace(py_exception_string);
1766
- }
1767
-
1768
- adaptor->ScheduleCallbackOnNodeEventLoop(
1769
- [deferred = std::move(deferred),
1770
- json = std::move(json),
1771
- exception = std::move(exception)](Napi::Env env) {
1772
- if (json.has_value()) {
1773
- deferred->Resolve(
1774
- Napi::String::New(env, *json));
1775
- } else {
1776
- deferred->Reject(
1777
- Napi::Error::New(env, *exception).Value());
1778
- }
1779
- });
1780
-
1781
- delete py_task;
1782
- }));
1851
+ json_task_id]() {
1852
+ return py::module::import("reboot.nodejs.python")
1853
+ .attr("task_await")(
1854
+ py_context,
1855
+ py::module::import(rbt_module.c_str())
1856
+ .attr(state_name.c_str()),
1857
+ method,
1858
+ json_task_id);
1859
+ },
1860
+ [](py::object py_json) {
1861
+ return py_json.cast<std::string>();
1862
+ },
1863
+ [](Napi::Env env, std::string&& json) {
1864
+ return Napi::String::New(env, json);
1783
1865
  });
1784
-
1785
- return promise;
1786
1866
  }
1787
1867
 
1788
1868
  Napi::Value ExternalContext_constructor(const Napi::CallbackInfo& info) {
@@ -1816,11 +1896,8 @@ Napi::Value ExternalContext_constructor(const Napi::CallbackInfo& info) {
1816
1896
  .Utf8Value();
1817
1897
  }
1818
1898
 
1819
- std::promise<py::object*> promise;
1820
-
1821
- adaptor->ScheduleCallbackOnPythonEventLoop(
1822
- [&promise,
1823
- &name,
1899
+ py::object* py_external_context = RunCallbackOnPythonEventLoop(
1900
+ [&name,
1824
1901
  &url,
1825
1902
  &bearer_token,
1826
1903
  &idempotency_seed,
@@ -1830,8 +1907,7 @@ Napi::Value ExternalContext_constructor(const Napi::CallbackInfo& info) {
1830
1907
  "reboot.aio.external");
1831
1908
 
1832
1909
  auto convert_str =
1833
- [](
1834
- const std::optional<std::string>& optional) -> py::object {
1910
+ [](const std::optional<std::string>& optional) -> py::object {
1835
1911
  if (optional.has_value()) {
1836
1912
  return py::str(*optional);
1837
1913
  } else {
@@ -1839,30 +1915,20 @@ Napi::Value ExternalContext_constructor(const Napi::CallbackInfo& info) {
1839
1915
  }
1840
1916
  };
1841
1917
 
1842
- promise.set_value(
1843
- new py::object(py_external.attr("ExternalContext")(
1844
- "name"_a = py::str(name),
1845
- "url"_a = convert_str(url),
1846
- "bearer_token"_a = convert_str(bearer_token),
1847
- "idempotency_seed"_a = convert_str(idempotency_seed),
1848
- "idempotency_required"_a = py::bool_(idempotency_required),
1849
- "idempotency_required_reason"_a = convert_str(
1850
- idempotency_required_reason))));
1918
+ return new py::object(py_external.attr("ExternalContext")(
1919
+ "name"_a = py::str(name),
1920
+ "url"_a = convert_str(url),
1921
+ "bearer_token"_a = convert_str(bearer_token),
1922
+ "idempotency_seed"_a = convert_str(idempotency_seed),
1923
+ "idempotency_required"_a = py::bool_(idempotency_required),
1924
+ "idempotency_required_reason"_a = convert_str(
1925
+ idempotency_required_reason)));
1851
1926
  });
1852
- py::object* py_external_context = promise.get_future().get();
1853
1927
 
1854
- Napi::External<py::object> js_external_context =
1855
- Napi::External<py::object>::New(
1856
- info.Env(),
1857
- py_external_context,
1858
- [](Napi::Env, py::object* py_external_context) {
1859
- adaptor->ScheduleCallbackOnPythonEventLoop(
1860
- [py_external_context]() {
1861
- delete py_external_context;
1862
- });
1863
- });
1864
-
1865
- js_external_context.TypeTag(&reboot_aio_external_ExternalContext);
1928
+ Napi::External<py::object> js_external_context = make_napi_external(
1929
+ info.Env(),
1930
+ py_external_context,
1931
+ &reboot_aio_external_ExternalContext);
1866
1932
 
1867
1933
  return js_external_context;
1868
1934
  }
@@ -1873,62 +1939,190 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
1873
1939
 
1874
1940
  Napi::Array js_servicers = info[1].As<Napi::Array>();
1875
1941
 
1876
- auto env = info.Env();
1877
- auto servicer_details = make_servicer_details(env, js_servicers);
1942
+ Napi::Object js_web_framework = info[2].As<Napi::Object>();
1943
+
1944
+ auto js_web_framework_start = NapiSafeFunctionReference(
1945
+ js_web_framework.Get("start").As<Napi::Function>());
1946
+
1947
+ auto js_web_framework_stop = NapiSafeFunctionReference(
1948
+ js_web_framework.Get("stop").As<Napi::Function>());
1949
+
1950
+ auto servicer_details = make_servicer_details(info.Env(), js_servicers);
1878
1951
 
1879
1952
  auto js_initialize = NapiSafeFunctionReference(
1880
- info[2].As<Napi::Function>());
1953
+ info[3].As<Napi::Function>());
1881
1954
 
1882
1955
  std::optional<std::string> initialize_bearer_token;
1883
- if (!info[3].IsUndefined()) {
1884
- initialize_bearer_token = info[3].As<Napi::String>().Utf8Value();
1956
+ if (!info[4].IsUndefined()) {
1957
+ initialize_bearer_token = info[4].As<Napi::String>().Utf8Value();
1885
1958
  }
1886
1959
 
1887
1960
  std::optional<NapiSafeObjectReference> js_token_verifier;
1888
- if (!info[4].IsUndefined()) {
1889
- js_token_verifier = NapiSafeReference(info[4].As<Napi::Object>());
1961
+ if (!info[5].IsUndefined()) {
1962
+ js_token_verifier = NapiSafeReference(info[5].As<Napi::Object>());
1890
1963
  }
1891
1964
 
1892
- std::promise<py::object*> promise;
1893
-
1894
- adaptor->ScheduleCallbackOnPythonEventLoop(
1895
- [&promise,
1896
- servicer_details = std::move(servicer_details),
1965
+ py::object* py_application = RunCallbackOnPythonEventLoop(
1966
+ [servicer_details = std::move(servicer_details),
1967
+ js_web_framework_start = std::move(js_web_framework_start),
1968
+ js_web_framework_stop = std::move(js_web_framework_stop),
1897
1969
  initialize_bearer_token = std::move(initialize_bearer_token),
1898
1970
  js_initialize = std::move(js_initialize),
1899
1971
  js_token_verifier,
1900
1972
  js_from_native_external = std::move(js_from_native_external)]() {
1901
1973
  py::list py_servicers = make_py_servicers(servicer_details);
1902
1974
 
1975
+ py::object py_web_framework_start = py::cpp_function(
1976
+ [js_web_framework_start = std::move(js_web_framework_start),
1977
+ js_from_native_external /* Need a copy because it is shared. */](
1978
+ std::string consensus_id,
1979
+ py::object py_port,
1980
+ py::object py_channel_manager) {
1981
+ std::optional<int> port;
1982
+ if (!py_port.is_none()) {
1983
+ port = py_port.cast<int>();
1984
+ }
1985
+
1986
+ return PythonFutureFromNodePromise(
1987
+ [js_web_framework_start = std::move(js_web_framework_start),
1988
+ js_from_native_external = std::move(js_from_native_external),
1989
+ consensus_id = std::move(consensus_id),
1990
+ port,
1991
+ py_channel_manager = new py::object(py_channel_manager)](
1992
+ Napi::Env env) mutable {
1993
+ Napi::External<py::object> js_external_channel_manager =
1994
+ make_napi_external(env, py_channel_manager);
1995
+
1996
+ Napi::Function js_create_external_context =
1997
+ Napi::Function::New(
1998
+ env,
1999
+ [js_from_native_external = std::move(
2000
+ js_from_native_external),
2001
+ // Ensures `py_channel_manager` remains valid.
2002
+ js_external_channel_manager = NapiSafeReference(
2003
+ js_external_channel_manager)](
2004
+ const Napi::CallbackInfo& info) mutable {
2005
+ Napi::Object js_args = info[0].As<Napi::Object>();
2006
+
2007
+ std::string name =
2008
+ js_args
2009
+ .Get("name")
2010
+ .As<Napi::String>()
2011
+ .Utf8Value();
2012
+
2013
+ std::optional<std::string> bearer_token;
2014
+ if (!js_args.Get("bearerToken").IsUndefined()) {
2015
+ bearer_token =
2016
+ js_args.Get("bearerToken")
2017
+ .As<Napi::String>()
2018
+ .Utf8Value();
2019
+ }
2020
+
2021
+ py::object* py_channel_manager =
2022
+ js_external_channel_manager
2023
+ .Value(info.Env())
2024
+ .Data();
2025
+
2026
+ return NodePromiseFromPythonCallback(
2027
+ info.Env(),
2028
+ [js_from_native_external,
2029
+ name = std::move(name),
2030
+ bearer_token = std::move(bearer_token),
2031
+ // Ensures `py_channel_manager`
2032
+ // remains valid. Need a copy
2033
+ // because
2034
+ // `js_create_external_context` may
2035
+ // get called more than once.
2036
+ js_external_channel_manager,
2037
+ py_channel_manager]() mutable {
2038
+ py::object py_bearer_token = py::none();
2039
+ if (bearer_token.has_value()) {
2040
+ py_bearer_token = py::str(*bearer_token);
2041
+ }
2042
+
2043
+ return new py::object(
2044
+ py::module::import(
2045
+ "reboot.aio.external")
2046
+ .attr("ExternalContext")(
2047
+ "name"_a = py::str(name),
2048
+ "channel_manager"_a =
2049
+ py_channel_manager,
2050
+ "bearer_token"_a =
2051
+ py_bearer_token));
2052
+ },
2053
+ [js_from_native_external = std::move(
2054
+ js_from_native_external)](
2055
+ Napi::Env env,
2056
+ py::object* py_context) {
2057
+ // (check_line_length skip)
2058
+ Napi::External<py::object> js_external_context =
2059
+ make_napi_external(
2060
+ env,
2061
+ py_context,
2062
+ // (check_line_length skip)
2063
+ &reboot_aio_external_ExternalContext);
2064
+
2065
+ return js_from_native_external
2066
+ .Value(env)
2067
+ .Call(
2068
+ env.Global(),
2069
+ {js_external_context});
2070
+ });
2071
+ });
2072
+
2073
+ return js_web_framework_start
2074
+ .Value(env)
2075
+ .Call(
2076
+ env.Global(),
2077
+ {Napi::String::New(env, consensus_id),
2078
+ port.has_value()
2079
+ ? Napi::Number::New(env, *port)
2080
+ : env.Null(),
2081
+ js_create_external_context})
2082
+ .As<Napi::Object>();
2083
+ },
2084
+ [](Napi::Env env, Napi::Value value) {
2085
+ Napi::Number js_port = value.As<Napi::Number>();
2086
+ return js_port.Int32Value();
2087
+ });
2088
+ });
2089
+
2090
+ py::object py_web_framework_stop = py::cpp_function(
2091
+ [js_web_framework_stop = std::move(js_web_framework_stop)](
2092
+ std::string consensus_id) {
2093
+ return PythonFutureFromNodePromise(
2094
+ [js_web_framework_stop = std::move(js_web_framework_stop),
2095
+ consensus_id = std::move(consensus_id)](
2096
+ Napi::Env env) mutable {
2097
+ return js_web_framework_stop
2098
+ .Value(env)
2099
+ .Call(
2100
+ env.Global(),
2101
+ {Napi::String::New(env, consensus_id)})
2102
+ .As<Napi::Object>();
2103
+ });
2104
+ });
2105
+
1903
2106
  py::object py_initialize = py::cpp_function(
1904
2107
  [js_initialize = std::move(js_initialize),
1905
- js_from_native_external = std::move(js_from_native_external)](
2108
+ js_from_native_external /* Need a copy because it is shared. */](
1906
2109
  py::object py_context) mutable {
1907
- py::object py_future =
1908
- py::module::import("asyncio").attr("Future")();
1909
-
1910
- adaptor->ScheduleCallbackOnNodeEventLoop(
1911
- [js_initialize, // NOTE: need a _copy_ of
1912
- // `js_initialize` here since
1913
- // `py_initialize` may be called more
1914
- // than once!
1915
- js_from_native_external = std::move(js_from_native_external),
1916
- py_context = new py::object(py_context),
1917
- py_future = new py::object(py_future)](
2110
+ return PythonFutureFromNodePromise(
2111
+ [js_initialize,
2112
+ js_from_native_external, // NOTE: need a _copy_ of
2113
+ // both `js_initialize`
2114
+ // and
2115
+ // `js_from_native_external`
2116
+ // here since
2117
+ // `py_initialize` may be
2118
+ // called more than once!
2119
+ py_context = new py::object(py_context)](
1918
2120
  Napi::Env env) mutable {
1919
2121
  Napi::External<py::object> js_external_context =
1920
- Napi::External<py::object>::New(
2122
+ make_napi_external(
1921
2123
  env,
1922
2124
  py_context,
1923
- [](Napi::Env, py::object* py_context) {
1924
- adaptor->ScheduleCallbackOnPythonEventLoop(
1925
- [py_context]() {
1926
- delete py_context;
1927
- });
1928
- });
1929
-
1930
- js_external_context.TypeTag(
1931
- &reboot_aio_external_ExternalContext);
2125
+ &reboot_aio_external_ExternalContext);
1932
2126
 
1933
2127
  Napi::Object js_context =
1934
2128
  js_from_native_external
@@ -1938,57 +2132,11 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
1938
2132
  {js_external_context})
1939
2133
  .As<Napi::Object>();
1940
2134
 
1941
- Napi::Object js_promise =
1942
- js_initialize
1943
- .Value(env)
1944
- .Call(env.Global(), {js_context})
1945
- .As<Napi::Object>();
1946
-
1947
- js_promise
1948
- .Get("then")
1949
- .As<Napi::Function>()
1950
- .Call(
1951
- js_promise,
1952
- {Napi::Function::New(
1953
- env,
1954
- [py_future](const Napi::CallbackInfo& info) {
1955
- adaptor->ScheduleCallbackOnPythonEventLoop(
1956
- [py_future]() {
1957
- bool cancelled =
1958
- py_future
1959
- ->attr("cancelled")()
1960
- .cast<bool>();
1961
- if (!cancelled) {
1962
- py_future->attr("set_result")(
1963
- py::none());
1964
- }
1965
- delete py_future;
1966
- });
1967
- }),
1968
- Napi::Function::New(
1969
- env,
1970
- [py_future](const Napi::CallbackInfo& info) {
1971
- std::string message = message_from_js_error(
1972
- info[0].As<Napi::Object>());
1973
-
1974
- adaptor->ScheduleCallbackOnPythonEventLoop(
1975
- [py_future,
1976
- message = std::move(message)]() {
1977
- bool cancelled =
1978
- py_future->attr("cancelled")()
1979
- .cast<bool>();
1980
- if (!cancelled) {
1981
- py_future->attr("set_exception")(
1982
- py::module::import("builtins")
1983
- .attr("Exception")(
1984
- message));
1985
- }
1986
- delete py_future;
1987
- });
1988
- })});
2135
+ return js_initialize
2136
+ .Value(env)
2137
+ .Call(env.Global(), {js_context})
2138
+ .As<Napi::Object>();
1989
2139
  });
1990
-
1991
- return py_future;
1992
2140
  });
1993
2141
 
1994
2142
  py::object py_initialize_bearer_token = py::none();
@@ -2001,44 +2149,22 @@ Napi::Value Application_constructor(const Napi::CallbackInfo& info) {
2001
2149
  py_token_verifier = make_py_token_verifier(*js_token_verifier);
2002
2150
  }
2003
2151
 
2004
- promise.set_value(
2005
- new py::object(
2006
- py::module::import("reboot.aio.applications")
2007
- .attr("Application")(
2008
- "servicers"_a = py_servicers,
2009
- "initialize"_a = py_initialize,
2010
- "initialize_bearer_token"_a =
2011
- py_initialize_bearer_token,
2012
- "token_verifier"_a = py_token_verifier)));
2013
- }
2014
- // TODO(benh): improve error handling mechanism to force all
2015
- // raised exceptions to be handled.
2016
- //
2017
- // [](py::error_already_set& e) {
2018
-
2019
- // },
2020
- // [](std::exception& e) {
2021
-
2022
- // },
2023
- // []() {
2024
-
2025
- // }
2026
- );
2027
-
2028
- py::object* py_application = promise.get_future().get();
2029
-
2030
- Napi::External<py::object> js_external_application =
2031
- Napi::External<py::object>::New(
2032
- info.Env(),
2033
- py_application,
2034
- [](Napi::Env, py::object* py_application) {
2035
- adaptor->ScheduleCallbackOnPythonEventLoop(
2036
- [py_application]() {
2037
- delete py_application;
2038
- });
2039
- });
2152
+ return new py::object(
2153
+ py::module::import("reboot.aio.applications")
2154
+ .attr("NodeApplication")(
2155
+ "servicers"_a = py_servicers,
2156
+ "web_framework_start"_a = py_web_framework_start,
2157
+ "web_framework_stop"_a = py_web_framework_stop,
2158
+ "initialize"_a = py_initialize,
2159
+ "initialize_bearer_token"_a =
2160
+ py_initialize_bearer_token,
2161
+ "token_verifier"_a = py_token_verifier));
2162
+ });
2040
2163
 
2041
- js_external_application.TypeTag(&reboot_aio_applications_Application);
2164
+ Napi::External<py::object> js_external_application = make_napi_external(
2165
+ info.Env(),
2166
+ py_application,
2167
+ &reboot_aio_applications_Application);
2042
2168
 
2043
2169
  return js_external_application;
2044
2170
  }
@@ -2061,58 +2187,45 @@ Napi::Value Application_run(const Napi::CallbackInfo& info) {
2061
2187
  py::object* py_application =
2062
2188
  js_external_application.Value(info.Env()).Data();
2063
2189
 
2064
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
2065
-
2066
- auto promise = deferred->Promise();
2067
-
2068
- adaptor->ScheduleCallbackOnPythonEventLoop(
2190
+ Napi::Promise js_promise = NodePromiseFromPythonTask(
2191
+ info.Env(),
2192
+ "Application.run() in nodejs",
2193
+ {"reboot.nodejs.python", "create_task"},
2069
2194
  [js_external_application, // Ensures `py_application` remains valid.
2070
- py_application,
2071
- deferred = std::move(deferred)]() {
2072
- py::object py_task =
2073
- py::module::import("reboot.nodejs.python")
2074
- .attr("create_task")(
2075
- py_application->attr("run")(),
2076
- "name"_a = "Application.run() in nodejs");
2077
-
2078
- py_task.attr("add_done_callback")(py::cpp_function(
2079
- [deferred = std::move(deferred),
2080
- // NOTE: need to keep a reference to `py_task` so that it
2081
- // doesn't get destroyed before completing!
2082
- py_task = new py::object(py_task)](py::object py_future) {
2083
- py::object py_exception = py_future.attr("exception")();
2084
-
2085
- std::optional<std::string> exception;
2086
-
2087
- if (!py_exception.is_none()) {
2088
- py::str py_exception_string = py_exception_str(py_exception);
2089
-
2090
- exception.emplace(py_exception_string);
2091
- }
2092
-
2093
- adaptor->ScheduleCallbackOnNodeEventLoop(
2094
- [deferred = std::move(deferred),
2095
- exception = std::move(exception)](Napi::Env env) {
2096
- if (!exception.has_value()) {
2097
- deferred->Resolve(env.Null());
2098
- } else {
2099
- env.Global()
2100
- .Get("process")
2101
- .As<Napi::Object>()
2102
- .Set("exitCode", 1);
2103
- deferred->Reject(
2104
- Napi::Error::New(env, *exception).Value());
2105
- }
2106
- // When `Application.run` exits, we unref our
2107
- // thread_safe_function to cause the runtime to exit.
2108
- adaptor->thread_safe_function.Unref(env);
2109
- });
2110
-
2111
- delete py_task;
2112
- }));
2195
+ py_application]() {
2196
+ return py_application->attr("run")();
2113
2197
  });
2114
2198
 
2115
- return promise;
2199
+ // More to do if `Application.run` returns ...
2200
+ js_promise
2201
+ .Get("then")
2202
+ .As<Napi::Function>()
2203
+ .Call(
2204
+ js_promise,
2205
+ {Napi::Function::New(
2206
+ info.Env(),
2207
+ [](const Napi::CallbackInfo& info) {
2208
+ // When `Application.run` exits, we unref our
2209
+ // thread_safe_function to cause the runtime to exit.
2210
+ adaptor->thread_safe_function.Unref(info.Env());
2211
+ }),
2212
+ Napi::Function::New(
2213
+ info.Env(),
2214
+ [](const Napi::CallbackInfo& info) {
2215
+ // There was an error, let's also set the
2216
+ // `process.exitCode` to reflect this.
2217
+ info.Env()
2218
+ .Global()
2219
+ .Get("process")
2220
+ .As<Napi::Object>()
2221
+ .Set("exitCode", 1);
2222
+
2223
+ // When `Application.run` exits, we unref our
2224
+ // thread_safe_function to cause the runtime to exit.
2225
+ adaptor->thread_safe_function.Unref(info.Env());
2226
+ })});
2227
+
2228
+ return js_promise;
2116
2229
  }
2117
2230
 
2118
2231
 
@@ -2124,22 +2237,18 @@ Napi::Value Context_auth(const Napi::CallbackInfo& info) {
2124
2237
 
2125
2238
  py::object* py_context = js_external_context.Data();
2126
2239
 
2127
- std::promise<std::optional<std::string>> promise;
2128
-
2129
- adaptor->ScheduleCallbackOnPythonEventLoop(
2130
- [py_context, &promise]() {
2240
+ std::optional<std::string> auth_bytes = RunCallbackOnPythonEventLoop(
2241
+ [py_context]() -> std::optional<std::string> {
2131
2242
  py::object py_auth = py_context->attr("auth");
2132
2243
 
2133
2244
  if (py_auth.is_none()) {
2134
- promise.set_value(std::nullopt);
2245
+ return std::nullopt;
2135
2246
  } else {
2136
2247
  std::string auth_bytes = py::bytes(py_auth.attr("to_proto_bytes")());
2137
- promise.set_value(auth_bytes);
2248
+ return auth_bytes;
2138
2249
  }
2139
2250
  });
2140
2251
 
2141
- std::optional<std::string> auth_bytes = promise.get_future().get();
2142
-
2143
2252
  if (auth_bytes.has_value()) {
2144
2253
  Napi::Env env = info.Env();
2145
2254
  return str_to_uint8array(env, *auth_bytes);
@@ -2157,15 +2266,13 @@ Napi::Value Context_stateId(const Napi::CallbackInfo& info) {
2157
2266
 
2158
2267
  py::object* py_context = js_external_context.Data();
2159
2268
 
2160
- std::promise<std::string> promise;
2161
-
2162
- adaptor->ScheduleCallbackOnPythonEventLoop(
2163
- [py_context, &promise]() {
2269
+ std::string state_id = RunCallbackOnPythonEventLoop(
2270
+ [py_context]() {
2164
2271
  py::str state_id = py_context->attr("state_id");
2165
- promise.set_value(std::string(state_id));
2272
+ return std::string(state_id);
2166
2273
  });
2167
2274
 
2168
- return Napi::String::New(info.Env(), promise.get_future().get());
2275
+ return Napi::String::New(info.Env(), state_id);
2169
2276
  }
2170
2277
 
2171
2278
 
@@ -2177,21 +2284,17 @@ Napi::Value Context_iteration(const Napi::CallbackInfo& info) {
2177
2284
 
2178
2285
  py::object* py_context = js_external_context.Data();
2179
2286
 
2180
- std::promise<std::optional<int>> promise;
2181
-
2182
- adaptor->ScheduleCallbackOnPythonEventLoop(
2183
- [py_context, &promise]() {
2287
+ std::optional<int> iteration = RunCallbackOnPythonEventLoop(
2288
+ [py_context]() -> std::optional<int> {
2184
2289
  py::object iteration = py_context->attr("iteration");
2185
2290
 
2186
2291
  if (iteration.is_none()) {
2187
- promise.set_value(std::optional<int>());
2292
+ return std::nullopt;
2188
2293
  } else {
2189
- promise.set_value(iteration.cast<int>());
2294
+ return iteration.cast<int>();
2190
2295
  }
2191
2296
  });
2192
2297
 
2193
- std::optional<int> iteration = promise.get_future().get();
2194
-
2195
2298
  if (iteration.has_value()) {
2196
2299
  return Napi::Number::New(info.Env(), *iteration);
2197
2300
  } else {
@@ -2200,6 +2303,41 @@ Napi::Value Context_iteration(const Napi::CallbackInfo& info) {
2200
2303
  }
2201
2304
 
2202
2305
 
2306
+ Napi::Value Context_cookie(const Napi::CallbackInfo& info) {
2307
+ Napi::External<py::object> js_external_context =
2308
+ info[0].As<Napi::External<py::object>>();
2309
+
2310
+ // CHECK(...CheckTypeTag(...));
2311
+
2312
+ py::object* py_context = js_external_context.Data();
2313
+
2314
+ std::string cookie = RunCallbackOnPythonEventLoop(
2315
+ [py_context]() {
2316
+ py::str cookie = py_context->attr("cookie");
2317
+ return std::string(cookie);
2318
+ });
2319
+
2320
+ return Napi::String::New(info.Env(), cookie);
2321
+ }
2322
+
2323
+
2324
+ Napi::Value Context_appInternal(const Napi::CallbackInfo& info) {
2325
+ Napi::External<py::object> js_external_context =
2326
+ info[0].As<Napi::External<py::object>>();
2327
+
2328
+ // CHECK(...CheckTypeTag(...));
2329
+
2330
+ py::object* py_context = js_external_context.Data();
2331
+
2332
+ bool app_internal = RunCallbackOnPythonEventLoop(
2333
+ [py_context]() {
2334
+ return py_context->attr("app_internal").cast<bool>();
2335
+ });
2336
+
2337
+ return Napi::Boolean::New(info.Env(), app_internal);
2338
+ }
2339
+
2340
+
2203
2341
  Napi::Value Context_generateIdempotentStateId(const Napi::CallbackInfo& info) {
2204
2342
  // NOTE: we immediately get a safe reference to the `Napi::External`
2205
2343
  // so that Node will not garbage collect it and the `py::object*` we
@@ -2231,19 +2369,15 @@ Napi::Value Context_generateIdempotentStateId(const Napi::CallbackInfo& info) {
2231
2369
  alias = js_alias.As<Napi::String>().Utf8Value();
2232
2370
  }
2233
2371
 
2234
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
2235
-
2236
- auto promise = deferred->Promise();
2237
-
2238
- adaptor->ScheduleCallbackOnPythonEventLoop(
2372
+ return NodePromiseFromPythonCallback(
2373
+ info.Env(),
2239
2374
  [js_external_context, // Ensures `py_context` remains valid.
2240
2375
  py_context,
2241
2376
  state_type = std::move(state_type),
2242
2377
  service_name = std::move(service_name),
2243
2378
  method = std::move(method),
2244
2379
  key = std::move(key),
2245
- alias = std::move(alias),
2246
- deferred = std::move(deferred)]() {
2380
+ alias = std::move(alias)]() {
2247
2381
  py::object py_key = py::none();
2248
2382
  if (key.has_value()) {
2249
2383
  py_key = py::cast(*key);
@@ -2259,34 +2393,20 @@ Napi::Value Context_generateIdempotentStateId(const Napi::CallbackInfo& info) {
2259
2393
  "key"_a = py_key,
2260
2394
  "alias"_a = py_alias));
2261
2395
 
2262
- py::object generate_idempotent_state_id =
2396
+ py::object py_generate_idempotent_state_id =
2263
2397
  py_context->attr("generate_idempotent_state_id");
2264
- try {
2265
- py::object result = generate_idempotent_state_id(
2266
- state_type,
2267
- service_name,
2268
- method,
2269
- py_idempotency);
2270
2398
 
2271
- auto state_id = result.cast<std::string>();
2399
+ py::object py_state_id = py_generate_idempotent_state_id(
2400
+ state_type,
2401
+ service_name,
2402
+ method,
2403
+ py_idempotency);
2272
2404
 
2273
- adaptor->ScheduleCallbackOnNodeEventLoop(
2274
- [state_id = std::move(state_id),
2275
- deferred = std::move(deferred)](Napi::Env env) {
2276
- deferred->Resolve(Napi::String::New(env, state_id));
2277
- });
2278
- } catch (pybind11::error_already_set& e) {
2279
- std::string exception_message = py_exception_str(e.value());
2280
- adaptor->ScheduleCallbackOnNodeEventLoop(
2281
- [exception_message = std::move(exception_message),
2282
- deferred = std::move(deferred)](Napi::Env env) {
2283
- deferred->Reject(
2284
- Napi::Error::New(env, exception_message).Value());
2285
- });
2286
- }
2405
+ return py_state_id.cast<std::string>();
2406
+ },
2407
+ [](Napi::Env env, std::string&& state_id) {
2408
+ return Napi::String::New(env, state_id);
2287
2409
  });
2288
-
2289
- return promise;
2290
2410
  }
2291
2411
 
2292
2412
 
@@ -2301,16 +2421,11 @@ Napi::Value WriterContext_set_sync(const Napi::CallbackInfo& info) {
2301
2421
 
2302
2422
  bool sync = info[1].As<Napi::Boolean>();
2303
2423
 
2304
- std::promise<void> promise;
2305
-
2306
- adaptor->ScheduleCallbackOnPythonEventLoop(
2307
- [&py_context, sync, &promise]() {
2424
+ RunCallbackOnPythonEventLoop(
2425
+ [&py_context, sync]() {
2308
2426
  py_context->attr("sync") = sync;
2309
- promise.set_value();
2310
2427
  });
2311
2428
 
2312
- promise.get_future().get();
2313
-
2314
2429
  return info.Env().Undefined();
2315
2430
  }
2316
2431
 
@@ -2326,121 +2441,34 @@ Napi::Value retry_reactively_until(const Napi::CallbackInfo& info) {
2326
2441
  auto js_condition = NapiSafeFunctionReference(
2327
2442
  info[1].As<Napi::Function>());
2328
2443
 
2329
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
2330
-
2331
- auto promise = deferred->Promise();
2332
-
2333
- adaptor->ScheduleCallbackOnPythonEventLoop(
2444
+ return NodePromiseFromPythonTaskWithContext(
2445
+ info.Env(),
2446
+ "retry_reactively_until(...) in nodejs",
2447
+ js_external_context,
2334
2448
  [js_external_context, // Ensures `py_context` remains valid.
2335
2449
  py_context,
2336
- js_condition = std::move(js_condition),
2337
- deferred = std::move(deferred)]() {
2450
+ js_condition = std::move(js_condition)]() {
2338
2451
  py::object py_condition = py::cpp_function(
2339
2452
  [js_condition = std::move(js_condition)]() mutable {
2340
- py::object py_future =
2341
- py::module::import("asyncio").attr("Future")();
2342
-
2343
- adaptor->ScheduleCallbackOnNodeEventLoop(
2344
- [js_condition, // NOTE: need a _copy_ of
2345
- // `js_condition` here since
2346
- // `py_condition` may be called more
2347
- // than once!
2348
- py_future = new py::object(py_future)](
2349
- Napi::Env env) mutable {
2350
- Napi::Object js_promise =
2351
- js_condition
2352
- .Value(env)
2353
- .Call(env.Global(), {})
2354
- .As<Napi::Object>();
2355
-
2356
- js_promise
2357
- .Get("then")
2358
- .As<Napi::Function>()
2359
- .Call(
2360
- js_promise,
2361
- {Napi::Function::New(
2362
- env,
2363
- [py_future](const Napi::CallbackInfo& info) {
2364
- bool result = info[0].As<Napi::Boolean>();
2365
- adaptor->ScheduleCallbackOnPythonEventLoop(
2366
- [py_future, result]() {
2367
- bool cancelled =
2368
- py_future->attr("cancelled")()
2369
- .cast<bool>();
2370
- if (!cancelled) {
2371
- py_future->attr("set_result")(
2372
- result);
2373
- }
2374
- delete py_future;
2375
- });
2376
- }),
2377
- Napi::Function::New(
2378
- env,
2379
- [py_future](const Napi::CallbackInfo& info) {
2380
- std::string message = message_from_js_error(
2381
- info[0].As<Napi::Object>());
2382
-
2383
- adaptor->ScheduleCallbackOnPythonEventLoop(
2384
- [py_future,
2385
- message = std::move(message)]() {
2386
- bool cancelled =
2387
- py_future->attr("cancelled")()
2388
- .cast<bool>();
2389
- if (!cancelled) {
2390
- py_future->attr("set_exception")(
2391
- py::module::import("builtins")
2392
- .attr("Exception")(
2393
- message));
2394
- }
2395
- delete py_future;
2396
- });
2397
- })});
2453
+ return PythonFutureFromNodePromise(
2454
+ // NOTE: need a _copy_ of
2455
+ // `js_condition` here since
2456
+ // `py_condition` may be called more
2457
+ // than once!
2458
+ [js_condition](Napi::Env env) mutable {
2459
+ return js_condition
2460
+ .Value(env)
2461
+ .Call(env.Global(), {})
2462
+ .As<Napi::Object>();
2463
+ },
2464
+ [](Napi::Env, Napi::Value value) {
2465
+ return value.As<Napi::Boolean>().Value();
2398
2466
  });
2399
-
2400
- return py_future;
2401
2467
  });
2402
2468
 
2403
- py::object py_task =
2404
- py::module::import("reboot.nodejs.python")
2405
- .attr("create_task_with_context")(
2406
- (py::module::import("reboot.aio.contexts")
2407
- .attr("retry_reactively_until"))(
2408
- py_context,
2409
- py_condition),
2410
- py_context,
2411
- "name"_a = "retry_reactively_until(...) in nodejs");
2412
-
2413
- py_task.attr("add_done_callback")(py::cpp_function(
2414
- [deferred = std::move(deferred),
2415
- // NOTE: need to keep a reference to `py_task` so that it
2416
- // doesn't get destroyed before completing!
2417
- py_task = new py::object(py_task)](py::object py_future) {
2418
- py::object py_exception = py_future.attr("exception")();
2419
-
2420
- std::optional<std::string> exception;
2421
-
2422
- if (!py_exception.is_none()) {
2423
- py::str py_exception_string = py_exception_str(py_exception);
2424
-
2425
- exception.emplace(py_exception_string);
2426
- }
2427
-
2428
- adaptor->ScheduleCallbackOnNodeEventLoop(
2429
- [deferred = std::move(deferred),
2430
- exception = std::move(exception)](Napi::Env env) {
2431
- if (!exception.has_value()) {
2432
- deferred->Resolve(env.Null());
2433
- } else {
2434
- deferred->Reject(
2435
- Napi::Error::New(env, *exception).Value());
2436
- }
2437
- });
2438
-
2439
- delete py_task;
2440
- }));
2469
+ return py::module::import("reboot.aio.contexts")
2470
+ .attr("retry_reactively_until")(py_context, py_condition);
2441
2471
  });
2442
-
2443
- return promise;
2444
2472
  }
2445
2473
 
2446
2474
 
@@ -2459,132 +2487,45 @@ Napi::Value atLeastOrMostOnce(const Napi::CallbackInfo& info) {
2459
2487
 
2460
2488
  bool at_most_once = info[3].As<Napi::Boolean>();
2461
2489
 
2462
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
2463
-
2464
- auto promise = deferred->Promise();
2465
-
2466
- adaptor->ScheduleCallbackOnPythonEventLoop(
2490
+ return NodePromiseFromPythonTaskWithContext(
2491
+ info.Env(),
2492
+ "memoize(...) in nodejs",
2493
+ js_external_context,
2467
2494
  [js_external_context, // Ensures `py_context` remains valid.
2468
2495
  py_context,
2469
2496
  js_callable = std::move(js_callable),
2470
2497
  idempotency_alias = std::move(idempotency_alias),
2471
- at_most_once,
2472
- deferred = std::move(deferred)]() {
2498
+ at_most_once]() {
2473
2499
  py::object py_callable = py::cpp_function(
2474
2500
  [js_callable = std::move(js_callable)]() mutable {
2475
- py::object py_future =
2476
- py::module::import("asyncio").attr("Future")();
2477
-
2478
- adaptor->ScheduleCallbackOnNodeEventLoop(
2479
- [js_callable, // NOTE: need a _copy_ of
2480
- // `js_callable` here since
2481
- // `py_callable` may be called more
2482
- // than once!
2483
- py_future = new py::object(py_future)](
2484
- Napi::Env env) mutable {
2485
- Napi::Object js_promise =
2486
- js_callable
2487
- .Value(env)
2488
- .Call(env.Global(), {})
2489
- .As<Napi::Object>();
2490
-
2491
- js_promise
2492
- .Get("then")
2493
- .As<Napi::Function>()
2494
- .Call(
2495
- js_promise,
2496
- {Napi::Function::New(
2497
- env,
2498
- [py_future](const Napi::CallbackInfo& info) {
2499
- std::string result =
2500
- info[0].As<Napi::String>().Utf8Value();
2501
- adaptor->ScheduleCallbackOnPythonEventLoop(
2502
- [py_future, result]() {
2503
- bool cancelled =
2504
- py_future->attr("cancelled")()
2505
- .cast<bool>();
2506
- if (!cancelled) {
2507
- py_future->attr("set_result")(
2508
- result);
2509
- }
2510
- delete py_future;
2511
- });
2512
- }),
2513
- Napi::Function::New(
2514
- env,
2515
- [py_future](const Napi::CallbackInfo& info) {
2516
- std::string message = message_from_js_error(
2517
- info[0].As<Napi::Object>());
2518
-
2519
- adaptor->ScheduleCallbackOnPythonEventLoop(
2520
- [py_future,
2521
- message = std::move(message)]() {
2522
- bool cancelled =
2523
- py_future->attr("cancelled")()
2524
- .cast<bool>();
2525
- if (!cancelled) {
2526
- py_future->attr("set_exception")(
2527
- py::module::import("builtins")
2528
- .attr("Exception")(
2529
- message));
2530
- }
2531
- delete py_future;
2532
- });
2533
- })});
2501
+ return PythonFutureFromNodePromise(
2502
+ // NOTE: need a _copy_ of `js_callable` here since
2503
+ // `py_callable` may be called more than once!
2504
+ [js_callable](Napi::Env env) mutable {
2505
+ return js_callable
2506
+ .Value(env)
2507
+ .Call(env.Global(), {})
2508
+ .As<Napi::Object>();
2509
+ },
2510
+ [](Napi::Env, Napi::Value value) {
2511
+ return std::string(value.As<Napi::String>().Utf8Value());
2534
2512
  });
2535
-
2536
- return py_future;
2537
2513
  });
2538
2514
 
2539
- py::object py_task =
2540
- py::module::import("reboot.nodejs.python")
2541
- .attr("create_task_with_context")(
2542
- (py::module::import("reboot.aio.memoize").attr("memoize"))(
2543
- py::str(idempotency_alias),
2544
- py_context,
2545
- py_callable,
2546
- "type_t"_a = py::eval("str"),
2547
- "at_most_once"_a = at_most_once),
2548
- py_context,
2549
- "name"_a = "memoize(...) in nodejs");
2550
-
2551
- py_task.attr("add_done_callback")(py::cpp_function(
2552
- [deferred = std::move(deferred),
2553
- // NOTE: need to keep a reference to `py_task` so that it
2554
- // doesn't get destroyed before completing!
2555
- py_task = new py::object(py_task)](py::object py_future) {
2556
- py::object py_exception = py_future.attr("exception")();
2557
-
2558
- std::optional<std::string> json;
2559
- std::optional<std::string> exception;
2560
-
2561
- if (py_exception.is_none()) {
2562
- py::str py_json = py_future.attr("result")();
2563
- json.emplace(py_json);
2564
- } else {
2565
- py::str py_exception_string = py_exception_str(py_exception);
2566
-
2567
- exception.emplace(py_exception_string);
2568
- }
2569
-
2570
- adaptor->ScheduleCallbackOnNodeEventLoop(
2571
- [deferred = std::move(deferred),
2572
- json = std::move(json),
2573
- exception = std::move(exception)](Napi::Env env) {
2574
- if (json.has_value()) {
2575
- deferred->Resolve(
2576
- Napi::String::New(env, *json));
2577
- } else {
2578
- deferred->Reject(
2579
- Napi::Error::New(env, *exception).Value());
2580
- }
2581
- });
2582
-
2583
- delete py_task;
2584
- }));
2515
+ return py::module::import("reboot.aio.memoize")
2516
+ .attr("memoize")(
2517
+ py::str(idempotency_alias),
2518
+ py_context,
2519
+ py_callable,
2520
+ "type_t"_a = py::eval("str"),
2521
+ "at_most_once"_a = at_most_once);
2522
+ },
2523
+ [](py::object py_json) {
2524
+ return py_json.cast<std::string>();
2525
+ },
2526
+ [](Napi::Env env, std::string&& json) {
2527
+ return Napi::String::New(env, json);
2585
2528
  });
2586
-
2587
- return promise;
2588
2529
  }
2589
2530
 
2590
2531
 
@@ -2603,60 +2544,22 @@ Napi::Value Servicer_read(const Napi::CallbackInfo& info) {
2603
2544
 
2604
2545
  py::object* py_context = js_external_context.Value(info.Env()).Data();
2605
2546
 
2606
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
2607
-
2608
- auto promise = deferred->Promise();
2609
-
2610
- adaptor->ScheduleCallbackOnPythonEventLoop(
2547
+ return NodePromiseFromPythonTaskWithContext(
2548
+ info.Env(),
2549
+ "servicer._read(...) in nodejs",
2550
+ js_external_context,
2611
2551
  [js_external_servicer, // Ensures `py_servicer` remains valid.
2612
2552
  py_servicer,
2613
2553
  js_external_context, // Ensures `py_context` remains valid.
2614
- py_context,
2615
- deferred = std::move(deferred)]() {
2616
- py::object py_task =
2617
- py::module::import("reboot.nodejs.python")
2618
- .attr("create_task_with_context")(
2619
- py_servicer->attr("_read")(py_context),
2620
- py_context,
2621
- "name"_a = "servicer._read(...) in nodejs");
2622
-
2623
- py_task.attr("add_done_callback")(py::cpp_function(
2624
- [deferred = std::move(deferred),
2625
- // NOTE: need to keep a reference to `py_task` so that it
2626
- // doesn't get destroyed before completing!
2627
- py_task = new py::object(py_task)](py::object py_future) {
2628
- py::object py_exception = py_future.attr("exception")();
2629
-
2630
- std::optional<std::string> json;
2631
- std::optional<std::string> exception;
2632
-
2633
- if (py_exception.is_none()) {
2634
- py::str py_json = py_future.attr("result")();
2635
- json.emplace(py_json);
2636
- } else {
2637
- py::str py_exception_string = py_exception_str(py_exception);
2638
-
2639
- exception.emplace(py_exception_string);
2640
- }
2641
-
2642
- adaptor->ScheduleCallbackOnNodeEventLoop(
2643
- [deferred = std::move(deferred),
2644
- json = std::move(json),
2645
- exception = std::move(exception)](Napi::Env env) {
2646
- if (json.has_value()) {
2647
- deferred->Resolve(
2648
- Napi::String::New(env, *json));
2649
- } else {
2650
- deferred->Reject(
2651
- Napi::Error::New(env, *exception).Value());
2652
- }
2653
- });
2654
-
2655
- delete py_task;
2656
- }));
2554
+ py_context]() {
2555
+ return py_servicer->attr("_read")(py_context);
2556
+ },
2557
+ [](py::object py_json) {
2558
+ return py_json.cast<std::string>();
2559
+ },
2560
+ [](Napi::Env env, std::string&& json) {
2561
+ return Napi::String::New(env, json);
2657
2562
  });
2658
-
2659
- return promise;
2660
2563
  }
2661
2564
 
2662
2565
 
@@ -2680,135 +2583,48 @@ Napi::Value Servicer_write(const Napi::CallbackInfo& info) {
2680
2583
 
2681
2584
  std::string json_options = info[3].As<Napi::String>().Utf8Value();
2682
2585
 
2683
- auto deferred = std::make_shared<Napi::Promise::Deferred>(info.Env());
2684
-
2685
- auto promise = deferred->Promise();
2686
-
2687
- adaptor->ScheduleCallbackOnPythonEventLoop(
2586
+ return NodePromiseFromPythonTaskWithContext(
2587
+ info.Env(),
2588
+ "servicer._write(...) in nodejs",
2589
+ js_external_context,
2688
2590
  [js_external_servicer, // Ensures `py_servicer` remains valid.
2689
2591
  py_servicer,
2690
2592
  js_external_context, // Ensures `py_context` remains valid.
2691
2593
  py_context,
2692
2594
  js_writer = std::move(js_writer),
2693
- json_options = std::move(json_options),
2694
- deferred = std::move(deferred)]() {
2595
+ json_options = std::move(json_options)]() {
2695
2596
  py::object py_writer = py::cpp_function(
2696
2597
  [js_writer = std::move(js_writer)](
2697
2598
  std::string state_json) mutable {
2698
- py::object py_future =
2699
- py::module::import("asyncio").attr("Future")();
2700
-
2701
- adaptor->ScheduleCallbackOnNodeEventLoop(
2599
+ return PythonFutureFromNodePromise(
2702
2600
  [js_writer, // NOTE: need a _copy_ of
2703
2601
  // `js_writer` here since
2704
2602
  // `py_writer` may be called more
2705
2603
  // than once!
2706
- state_json,
2707
- py_future = new py::object(py_future)](
2708
- Napi::Env env) mutable {
2709
- Napi::Object js_promise =
2710
- js_writer
2711
- .Value(env)
2712
- .Call(
2713
- env.Global(),
2714
- {Napi::String::New(env, state_json)})
2715
- .As<Napi::Object>();
2716
-
2717
- js_promise
2718
- .Get("then")
2719
- .As<Napi::Function>()
2604
+ state_json](Napi::Env env) mutable {
2605
+ return js_writer
2606
+ .Value(env)
2720
2607
  .Call(
2721
- js_promise,
2722
- {Napi::Function::New(
2723
- env,
2724
- [py_future](const Napi::CallbackInfo& info) {
2725
- std::string result =
2726
- info[0].As<Napi::String>().Utf8Value();
2727
- adaptor->ScheduleCallbackOnPythonEventLoop(
2728
- [py_future, result]() {
2729
- bool cancelled =
2730
- py_future->attr("cancelled")()
2731
- .cast<bool>();
2732
- if (!cancelled) {
2733
- py_future->attr("set_result")(
2734
- result);
2735
- }
2736
- delete py_future;
2737
- });
2738
- }),
2739
- Napi::Function::New(
2740
- env,
2741
- [py_future](const Napi::CallbackInfo& info) {
2742
- std::string message = message_from_js_error(
2743
- info[0].As<Napi::Object>());
2744
-
2745
- adaptor->ScheduleCallbackOnPythonEventLoop(
2746
- [py_future,
2747
- message = std::move(message)]() {
2748
- bool cancelled =
2749
- py_future->attr("cancelled")()
2750
- .cast<bool>();
2751
- if (!cancelled) {
2752
- py_future->attr("set_exception")(
2753
- py::module::import("builtins")
2754
- .attr("Exception")(
2755
- message));
2756
- }
2757
- delete py_future;
2758
- });
2759
- })});
2608
+ env.Global(),
2609
+ {Napi::String::New(env, state_json)})
2610
+ .As<Napi::Object>();
2611
+ },
2612
+ [](Napi::Env env, Napi::Value value) {
2613
+ return std::string(value.As<Napi::String>().Utf8Value());
2760
2614
  });
2761
-
2762
- return py_future;
2763
2615
  });
2764
2616
 
2765
- py::object py_task =
2766
- py::module::import("reboot.nodejs.python")
2767
- .attr("create_task_with_context")(
2768
- py_servicer->attr("_write")(
2769
- py_context,
2770
- py_writer,
2771
- json_options),
2772
- py_context,
2773
- "name"_a = "servicer._write(...) in nodejs");
2774
-
2775
- py_task.attr("add_done_callback")(py::cpp_function(
2776
- [deferred = std::move(deferred),
2777
- // NOTE: need to keep a reference to `py_task` so that it
2778
- // doesn't get destroyed before completing!
2779
- py_task = new py::object(py_task)](py::object py_future) {
2780
- py::object py_exception = py_future.attr("exception")();
2781
-
2782
- std::optional<std::string> result;
2783
- std::optional<std::string> exception;
2784
-
2785
- if (py_exception.is_none()) {
2786
- py::str py_result = py_future.attr("result")();
2787
- result.emplace(py_result);
2788
- } else {
2789
- py::str py_exception_string = py_exception_str(py_exception);
2790
-
2791
- exception.emplace(py_exception_string);
2792
- }
2793
-
2794
- adaptor->ScheduleCallbackOnNodeEventLoop(
2795
- [deferred = std::move(deferred),
2796
- result = std::move(result),
2797
- exception = std::move(exception)](Napi::Env env) {
2798
- if (result.has_value()) {
2799
- deferred->Resolve(
2800
- Napi::String::New(env, *result));
2801
- } else {
2802
- deferred->Reject(
2803
- Napi::Error::New(env, *exception).Value());
2804
- }
2805
- });
2806
-
2807
- delete py_task;
2808
- }));
2617
+ return py_servicer->attr("_write")(
2618
+ py_context,
2619
+ py_writer,
2620
+ json_options);
2621
+ },
2622
+ [](py::object py_result) {
2623
+ return py_result.cast<std::string>();
2624
+ },
2625
+ [](Napi::Env env, std::string&& result) {
2626
+ return Napi::String::New(env, result);
2809
2627
  });
2810
-
2811
- return promise;
2812
2628
  }
2813
2629
 
2814
2630
 
@@ -2874,6 +2690,14 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
2874
2690
  Napi::String::New(env, "Context_iteration"),
2875
2691
  Napi::Function::New<Context_iteration>(env));
2876
2692
 
2693
+ exports.Set(
2694
+ Napi::String::New(env, "Context_cookie"),
2695
+ Napi::Function::New<Context_cookie>(env));
2696
+
2697
+ exports.Set(
2698
+ Napi::String::New(env, "Context_appInternal"),
2699
+ Napi::Function::New<Context_appInternal>(env));
2700
+
2877
2701
  exports.Set(
2878
2702
  Napi::String::New(env, "Context_generateIdempotentStateId"),
2879
2703
  Napi::Function::New<Context_generateIdempotentStateId>(env));
@@ -2891,8 +2715,8 @@ Napi::Object Init(Napi::Env env, Napi::Object exports) {
2891
2715
  Napi::Function::New<atLeastOrMostOnce>(env));
2892
2716
 
2893
2717
  exports.Set(
2894
- Napi::String::New(env, "Future_await"),
2895
- Napi::Function::New<Future_await>(env));
2718
+ Napi::String::New(env, "Task_await"),
2719
+ Napi::Function::New<Task_await>(env));
2896
2720
 
2897
2721
  exports.Set(
2898
2722
  Napi::String::New(env, "ExternalContext_constructor"),