@temporalio/core-bridge 1.11.8 → 1.12.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.
Files changed (205) hide show
  1. package/Cargo.lock +219 -193
  2. package/Cargo.toml +27 -8
  3. package/README.md +5 -0
  4. package/index.js +72 -12
  5. package/lib/errors.d.ts +25 -0
  6. package/lib/errors.js +76 -1
  7. package/lib/errors.js.map +1 -1
  8. package/lib/index.d.ts +11 -478
  9. package/lib/index.js +28 -5
  10. package/lib/index.js.map +1 -1
  11. package/lib/native.d.ts +330 -0
  12. package/lib/{worker-tuner.js → native.js} +1 -1
  13. package/lib/native.js.map +1 -0
  14. package/package.json +7 -3
  15. package/releases/aarch64-apple-darwin/index.node +0 -0
  16. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  17. package/releases/x86_64-apple-darwin/index.node +0 -0
  18. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  19. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  20. package/sdk-core/.cargo/config.toml +8 -2
  21. package/sdk-core/.cargo/multi-worker-manual-test +15 -0
  22. package/sdk-core/.github/workflows/per-pr.yml +40 -11
  23. package/sdk-core/AGENTS.md +73 -0
  24. package/sdk-core/ARCHITECTURE.md +71 -23
  25. package/sdk-core/Cargo.toml +1 -1
  26. package/sdk-core/README.md +4 -4
  27. package/sdk-core/arch_docs/workflow_task_chunking.md +51 -0
  28. package/sdk-core/client/Cargo.toml +1 -1
  29. package/sdk-core/client/src/lib.rs +49 -13
  30. package/sdk-core/client/src/metrics.rs +15 -16
  31. package/sdk-core/client/src/proxy.rs +2 -2
  32. package/sdk-core/client/src/raw.rs +54 -8
  33. package/sdk-core/client/src/retry.rs +109 -13
  34. package/sdk-core/client/src/worker_registry/mod.rs +4 -4
  35. package/sdk-core/client/src/workflow_handle/mod.rs +1 -1
  36. package/sdk-core/core/Cargo.toml +28 -8
  37. package/sdk-core/core/src/abstractions.rs +62 -10
  38. package/sdk-core/core/src/core_tests/activity_tasks.rs +180 -8
  39. package/sdk-core/core/src/core_tests/mod.rs +4 -4
  40. package/sdk-core/core/src/core_tests/queries.rs +18 -4
  41. package/sdk-core/core/src/core_tests/workers.rs +3 -3
  42. package/sdk-core/core/src/core_tests/workflow_tasks.rs +191 -25
  43. package/sdk-core/core/src/ephemeral_server/mod.rs +10 -3
  44. package/sdk-core/core/src/internal_flags.rs +14 -14
  45. package/sdk-core/core/src/lib.rs +5 -2
  46. package/sdk-core/core/src/pollers/mod.rs +1 -1
  47. package/sdk-core/core/src/pollers/poll_buffer.rs +495 -164
  48. package/sdk-core/core/src/protosext/mod.rs +3 -3
  49. package/sdk-core/core/src/replay/mod.rs +3 -3
  50. package/sdk-core/core/src/telemetry/metrics.rs +13 -4
  51. package/sdk-core/core/src/telemetry/mod.rs +72 -70
  52. package/sdk-core/core/src/telemetry/otel.rs +51 -54
  53. package/sdk-core/core/src/test_help/mod.rs +9 -3
  54. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +31 -11
  55. package/sdk-core/core/src/worker/activities/local_activities.rs +35 -28
  56. package/sdk-core/core/src/worker/activities.rs +58 -30
  57. package/sdk-core/core/src/worker/client/mocks.rs +3 -3
  58. package/sdk-core/core/src/worker/client.rs +155 -53
  59. package/sdk-core/core/src/worker/mod.rs +103 -95
  60. package/sdk-core/core/src/worker/nexus.rs +100 -73
  61. package/sdk-core/core/src/worker/tuner/resource_based.rs +14 -6
  62. package/sdk-core/core/src/worker/tuner.rs +4 -13
  63. package/sdk-core/core/src/worker/workflow/history_update.rs +47 -51
  64. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +1 -4
  65. package/sdk-core/core/src/worker/workflow/machines/nexus_operation_state_machine.rs +127 -32
  66. package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +1 -2
  67. package/sdk-core/core/src/worker/workflow/machines/update_state_machine.rs +1 -1
  68. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +55 -42
  69. package/sdk-core/core/src/worker/workflow/managed_run.rs +45 -35
  70. package/sdk-core/core/src/worker/workflow/mod.rs +200 -97
  71. package/sdk-core/core/src/worker/workflow/wft_poller.rs +175 -4
  72. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +38 -36
  73. package/sdk-core/core-api/Cargo.toml +8 -0
  74. package/sdk-core/core-api/src/envconfig.rs +1544 -0
  75. package/sdk-core/core-api/src/lib.rs +2 -0
  76. package/sdk-core/core-api/src/telemetry/metrics.rs +8 -0
  77. package/sdk-core/core-api/src/telemetry.rs +36 -3
  78. package/sdk-core/core-api/src/worker.rs +301 -75
  79. package/sdk-core/docker/docker-compose-telem.yaml +1 -0
  80. package/sdk-core/etc/prometheus.yaml +6 -2
  81. package/sdk-core/histories/long_local_activity_with_update-0_history.bin +0 -0
  82. package/sdk-core/histories/long_local_activity_with_update-1_history.bin +0 -0
  83. package/sdk-core/histories/long_local_activity_with_update-2_history.bin +0 -0
  84. package/sdk-core/histories/long_local_activity_with_update-3_history.bin +0 -0
  85. package/sdk-core/sdk/src/activity_context.rs +5 -0
  86. package/sdk-core/sdk/src/interceptors.rs +73 -3
  87. package/sdk-core/sdk/src/lib.rs +15 -16
  88. package/sdk-core/sdk/src/workflow_context/options.rs +10 -0
  89. package/sdk-core/sdk/src/workflow_context.rs +48 -29
  90. package/sdk-core/sdk/src/workflow_future.rs +5 -6
  91. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/.github/workflows/push-to-buf.yml +20 -0
  92. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/CODEOWNERS +6 -0
  93. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/README.md +17 -6
  94. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/VERSION +1 -1
  95. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.lock +7 -2
  96. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/buf.yaml +2 -0
  97. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/request_response.proto +78 -0
  98. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/cloudservice/v1/service.proto +29 -0
  99. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/identity/v1/message.proto +74 -32
  100. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/namespace/v1/message.proto +45 -15
  101. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/nexus/v1/message.proto +7 -1
  102. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/operation/v1/message.proto +3 -3
  103. package/sdk-core/sdk-core-protos/protos/api_cloud_upstream/temporal/api/cloud/region/v1/message.proto +3 -3
  104. package/sdk-core/sdk-core-protos/protos/api_upstream/LICENSE +1 -1
  105. package/sdk-core/sdk-core-protos/protos/api_upstream/buf.yaml +2 -0
  106. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +1103 -88
  107. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +1233 -151
  108. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/activity/v1/message.proto +0 -22
  109. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/batch/v1/message.proto +19 -24
  110. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/command/v1/message.proto +0 -22
  111. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +12 -22
  112. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/deployment/v1/message.proto +45 -45
  113. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +0 -22
  114. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/command_type.proto +0 -22
  115. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/common.proto +15 -22
  116. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/deployment.proto +0 -22
  117. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/event_type.proto +4 -22
  118. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +0 -22
  119. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/namespace.proto +0 -22
  120. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/nexus.proto +0 -20
  121. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/query.proto +0 -22
  122. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +0 -22
  123. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/schedule.proto +0 -22
  124. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +0 -22
  125. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/update.proto +0 -22
  126. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/workflow.proto +4 -22
  127. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/errordetails/v1/message.proto +0 -22
  128. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/export/v1/message.proto +0 -22
  129. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +2 -22
  130. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/filter/v1/message.proto +0 -22
  131. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +75 -49
  132. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/namespace/v1/message.proto +0 -22
  133. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +0 -20
  134. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +0 -22
  135. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +0 -22
  136. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/protocol/v1/message.proto +0 -22
  137. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/query/v1/message.proto +0 -22
  138. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/replication/v1/message.proto +0 -22
  139. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/rules/v1/message.proto +90 -0
  140. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +0 -22
  141. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/enhanced_stack_trace.proto +0 -22
  142. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/task_complete_metadata.proto +0 -22
  143. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/user_metadata.proto +0 -22
  144. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/workflow_metadata.proto +0 -22
  145. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +17 -38
  146. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/update/v1/message.proto +0 -22
  147. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/version/v1/message.proto +0 -22
  148. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +151 -44
  149. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +172 -65
  150. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +69 -28
  151. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/activity_task/activity_task.proto +18 -0
  152. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/common/common.proto +5 -0
  153. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/nexus/nexus.proto +16 -1
  154. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +21 -15
  155. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +3 -0
  156. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +3 -0
  157. package/sdk-core/sdk-core-protos/src/lib.rs +60 -16
  158. package/sdk-core/test-utils/src/lib.rs +157 -39
  159. package/sdk-core/tests/cloud_tests.rs +86 -0
  160. package/sdk-core/tests/fuzzy_workflow.rs +23 -26
  161. package/sdk-core/tests/global_metric_tests.rs +116 -0
  162. package/sdk-core/tests/heavy_tests.rs +127 -7
  163. package/sdk-core/tests/integ_tests/client_tests.rs +2 -8
  164. package/sdk-core/tests/integ_tests/metrics_tests.rs +100 -106
  165. package/sdk-core/tests/integ_tests/polling_tests.rs +94 -8
  166. package/sdk-core/tests/integ_tests/update_tests.rs +75 -6
  167. package/sdk-core/tests/integ_tests/worker_tests.rs +54 -5
  168. package/sdk-core/tests/integ_tests/worker_versioning_tests.rs +240 -0
  169. package/sdk-core/tests/integ_tests/workflow_tests/determinism.rs +41 -3
  170. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +168 -8
  171. package/sdk-core/tests/integ_tests/workflow_tests/nexus.rs +285 -15
  172. package/sdk-core/tests/integ_tests/workflow_tests/priority.rs +12 -4
  173. package/sdk-core/tests/integ_tests/workflow_tests/stickyness.rs +3 -2
  174. package/sdk-core/tests/integ_tests/workflow_tests.rs +124 -74
  175. package/sdk-core/tests/main.rs +3 -51
  176. package/sdk-core/tests/manual_tests.rs +430 -0
  177. package/sdk-core/tests/runner.rs +28 -2
  178. package/src/client.rs +565 -0
  179. package/src/helpers/abort_controller.rs +204 -0
  180. package/src/helpers/callbacks.rs +299 -0
  181. package/src/helpers/errors.rs +302 -0
  182. package/src/helpers/future.rs +44 -0
  183. package/src/helpers/handles.rs +191 -0
  184. package/src/helpers/inspect.rs +18 -0
  185. package/src/helpers/json_string.rs +58 -0
  186. package/src/helpers/mod.rs +20 -0
  187. package/src/helpers/properties.rs +71 -0
  188. package/src/helpers/try_from_js.rs +213 -0
  189. package/src/helpers/try_into_js.rs +129 -0
  190. package/src/lib.rs +28 -40
  191. package/src/logs.rs +111 -0
  192. package/src/metrics.rs +325 -0
  193. package/src/runtime.rs +409 -498
  194. package/src/testing.rs +315 -57
  195. package/src/worker.rs +907 -378
  196. package/ts/errors.ts +57 -0
  197. package/ts/index.ts +10 -596
  198. package/ts/native.ts +496 -0
  199. package/lib/worker-tuner.d.ts +0 -167
  200. package/lib/worker-tuner.js.map +0 -1
  201. package/src/conversions/slot_supplier_bridge.rs +0 -291
  202. package/src/conversions.rs +0 -618
  203. package/src/errors.rs +0 -38
  204. package/src/helpers.rs +0 -297
  205. package/ts/worker-tuner.ts +0 -193
@@ -0,0 +1,204 @@
1
+ use std::sync::{
2
+ Arc, OnceLock,
3
+ atomic::{AtomicU8, Ordering, fence},
4
+ };
5
+
6
+ use neon::{
7
+ handle::{Handle, Root},
8
+ object::Object,
9
+ prelude::{Context, JsResult},
10
+ types::{JsFunction, JsObject, JsValue},
11
+ };
12
+
13
+ use super::{BridgeResult, JsCallback, TryIntoJs, errors::IntoThrow as _};
14
+
15
+ pub type JsAbortController = JsObject;
16
+ pub type JsAbortSignal = JsValue;
17
+
18
+ /// An object that models a JavaScript `AbortController`, and its corresponding `AbortSignal`,
19
+ /// allowing the Rust side to fire that signal if/when needed, e.g. when dropped from the Rust side.
20
+ ///
21
+ /// The JS counterpart object is lazily instantiated when the signal gets converted to JS (through
22
+ /// the `TryIntoJs` trait); this ensures that the Rust side can be created without a JS `Context`.
23
+ pub struct AbortController {
24
+ inner: Arc<AbortControllerInner>,
25
+ drop_abort_reason: String,
26
+ }
27
+
28
+ /// An object that models the signal of a JavaScript `AbortController`.
29
+ pub struct AbortSignal {
30
+ inner: Arc<AbortControllerInner>,
31
+ }
32
+
33
+ /// The inner state of an `AbortController`, shared between the Rust and JS sides.
34
+ struct AbortControllerInner {
35
+ // The fact that we require a `Context` to initialize the JS counterpart means that we are running
36
+ // on the Node's thread, which guarantees that there can't be multiple threads calling into that
37
+ // function concurrently; that should in theory aleviate the need to use a lock on `js_counterpart`.
38
+ //
39
+ // It is however possible for the rust-side controller to get aborted from a non-Node thread
40
+ // while the JS-side controller is being created on the Node thread, in which case we don't
41
+ // want the Rust-side thread to get blocked for the JS-side to complete instantiation.
42
+ //
43
+ // By modelling the "JS initialization" and "is aborted" states as two distinct independant
44
+ // structures, we ensure that we're never blocking execution of either thread. This however
45
+ // means that either step may happen before the other, so we need to be careful not to miss
46
+ // sending the abort signal. The good news is that nothing bad will happen if we call the JS
47
+ // abort callback multiple times.
48
+ js_counterpart: OnceLock<AbortControllerJsCounterpart>,
49
+ aborted: OnceLock<String>,
50
+
51
+ state: AtomicU8,
52
+ }
53
+
54
+ struct AbortControllerJsCounterpart {
55
+ controller: Root<JsObject>,
56
+ abort: JsCallback<(String,), ()>,
57
+ }
58
+
59
+ const STATE_UNINITIALIZED: u8 = 0;
60
+ const STATE_ARMED: u8 = 1 << 0;
61
+ const STATE_ABORTED: u8 = 1 << 1;
62
+ const STATE_DISARMED: u8 = STATE_ARMED | STATE_ABORTED;
63
+
64
+ impl AbortController {
65
+ /// Create a new `AbortController`.
66
+ ///
67
+ /// The `drop_abort_reason` string will be used as the reason for the abort
68
+ /// if the controller is dropped from the Rust side.
69
+ #[must_use]
70
+ pub fn new(drop_abort_reason: String) -> Self {
71
+ let inner = AbortControllerInner {
72
+ js_counterpart: OnceLock::new(),
73
+ aborted: OnceLock::new(),
74
+ state: AtomicU8::new(STATE_UNINITIALIZED),
75
+ };
76
+ let inner = Arc::new(inner);
77
+ Self {
78
+ inner,
79
+ drop_abort_reason,
80
+ }
81
+ }
82
+
83
+ /// Get an associated `AbortSignal` object for this controller. This method can
84
+ /// be called at any time (i.e. even after the controller has been cancelled),
85
+ /// any number of times (i.e. to pass a same signal to multiple JS functions),
86
+ /// and from any thread.
87
+ #[must_use]
88
+ pub fn get_signal(&self) -> AbortSignal {
89
+ AbortSignal {
90
+ inner: self.inner.clone(),
91
+ }
92
+ }
93
+
94
+ /// Abort the controller, causing the JS side `signal` to fire.
95
+ ///
96
+ /// This method can be called at any time (i.e. even before the controller has been armed,
97
+ /// or after it has been disarmed), and from any thread.
98
+ pub fn abort(&self, reason: impl Into<String>) {
99
+ self.inner.abort(reason);
100
+ }
101
+
102
+ /// Disarm the controller, so that it can no longer be aborted.
103
+ ///
104
+ /// Once a controller has been disarmed, it's abort status will not change anymore. It is
105
+ /// recommended to call this method once it is known that the signal is no longer needed
106
+ /// on the JS side, e.g. when the called JS function has returned, as this will prevent the
107
+ /// overhead of implicit abortion when the controller is dropped.
108
+ pub fn disarm(&self) {
109
+ self.inner.disarm();
110
+ }
111
+ }
112
+
113
+ impl Drop for AbortController {
114
+ fn drop(&mut self) {
115
+ self.abort(self.drop_abort_reason.clone());
116
+ }
117
+ }
118
+
119
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
120
+
121
+ impl TryIntoJs for AbortSignal {
122
+ type Output = JsAbortSignal;
123
+ fn try_into_js<'cx>(self, cx: &mut impl Context<'cx>) -> JsResult<'cx, JsAbortSignal> {
124
+ let controller = self.inner.get_or_init_controller(cx).into_throw(cx)?;
125
+ controller.get(cx, "signal")
126
+ }
127
+ }
128
+
129
+ impl AbortControllerInner {
130
+ /// Create the JS `AbortController` if it hasn't been created yet.
131
+ /// Returns a reference to the signal object that can be passed to JS.
132
+ fn get_or_init_controller<'cx, C: Context<'cx>>(
133
+ &self,
134
+ cx: &mut C,
135
+ ) -> BridgeResult<Handle<'cx, JsAbortController>> {
136
+ if let Some(js_counterpart) = self.js_counterpart.get() {
137
+ // Already initialized, return the controller
138
+ return Ok(js_counterpart.controller.to_inner(cx).upcast());
139
+ }
140
+
141
+ // Not initialized yet, create the JS AbortController
142
+ let global = cx.global_object();
143
+ let abort_controller_class = global.get::<JsFunction, _, _>(cx, "AbortController")?;
144
+ let js_controller = abort_controller_class.construct(cx, [])?;
145
+
146
+ let abort_fn = js_controller.get::<JsFunction, _, _>(cx, "abort")?;
147
+ let abort_cb = JsCallback::new(cx, abort_fn, Some(js_controller));
148
+
149
+ let js_counterpart = AbortControllerJsCounterpart {
150
+ controller: js_controller.root(cx),
151
+ abort: abort_cb,
152
+ };
153
+
154
+ let controller = match self.js_counterpart.set(js_counterpart) {
155
+ Ok(()) => {
156
+ // Ordering: Write to `state` implies previous write to `js_counterpart` is visible to other threads
157
+ if self.state.fetch_or(STATE_ARMED, Ordering::Release) == STATE_ABORTED {
158
+ // Ordering: Previous concurrent write to `aborted` must be visible at this point
159
+ fence(Ordering::Acquire);
160
+
161
+ // The controller was aborted before it was armed; immediately call the abort callback
162
+
163
+ // Fire and forget
164
+ let _ = self
165
+ .js_counterpart
166
+ .get()
167
+ .unwrap()
168
+ .abort
169
+ .call(cx, (self.aborted.get().unwrap().clone(),));
170
+ }
171
+ js_controller
172
+ }
173
+ Err(js_counterpart) => js_counterpart.controller.to_inner(cx).upcast(),
174
+ };
175
+
176
+ Ok(controller)
177
+ }
178
+
179
+ /// Immediately abort the `AbortController`, causing the JS side `signal` to fire.
180
+ fn abort(&self, reason: impl Into<String>) {
181
+ let reason = reason.into();
182
+ if self.aborted.set(reason.clone()) == Ok(()) {
183
+ // If we haven't created the JS AbortController yet, there's nothing to abort
184
+ // Ordering: Write to `state` implies previous write to `aborted` is visible to other threads
185
+ if self.state.fetch_or(STATE_ABORTED, Ordering::Release) == STATE_ARMED {
186
+ // Ordering: Previous concurrent write to `js_counterpart` must be visible at this point
187
+ fence(Ordering::Acquire);
188
+
189
+ // Fire and forget
190
+ let _ = self
191
+ .js_counterpart
192
+ .get()
193
+ .unwrap()
194
+ .abort
195
+ .call_on_js_thread((reason,));
196
+ }
197
+ }
198
+ }
199
+
200
+ fn disarm(&self) {
201
+ // Ordering: this requires no dependency on any other state
202
+ self.state.store(STATE_DISARMED, Ordering::Relaxed);
203
+ }
204
+ }
@@ -0,0 +1,299 @@
1
+ use std::{marker::PhantomData, sync::Arc};
2
+
3
+ use neon::{
4
+ event::Channel,
5
+ handle::{Handle, Root},
6
+ object::Object,
7
+ prelude::Context,
8
+ types::{JsFunction, JsFuture, JsObject, JsPromise, JsValue, Value as _},
9
+ };
10
+ use tracing::error;
11
+
12
+ use super::{BridgeError, BridgeResult, TryFromJs, TryIntoJs, errors::IntoThrow as _};
13
+
14
+ /// A callback is a JS function that is meant to be called by the Rust side.
15
+ /// A `JsCallback` is a callback that returns a value synchronously.
16
+ #[derive(Debug)]
17
+ pub struct JsCallback<Args, Ret>
18
+ where
19
+ Args: TryIntoJsArgs + Send + Sync,
20
+ Ret: TryFromJs + Send + Sync,
21
+ {
22
+ inner: Arc<CallbackInner<Args, Ret>>,
23
+ }
24
+
25
+ impl<Args, Ret> Clone for JsCallback<Args, Ret>
26
+ where
27
+ Args: TryIntoJsArgs + Send + Sync,
28
+ Ret: TryFromJs + Send + Sync,
29
+ {
30
+ fn clone(&self) -> Self {
31
+ Self {
32
+ inner: self.inner.clone(),
33
+ }
34
+ }
35
+ }
36
+
37
+ impl<Args, Ret> JsCallback<Args, Ret>
38
+ where
39
+ Args: TryIntoJsArgs + Send + Sync + 'static,
40
+ Ret: TryFromJs + Send + Sync + 'static,
41
+ {
42
+ pub fn new<'cx, C: Context<'cx>>(
43
+ cx: &mut C,
44
+ func: Handle<JsFunction>,
45
+ this: Option<Handle<JsObject>>,
46
+ ) -> Self {
47
+ Self {
48
+ inner: Arc::new(CallbackInner {
49
+ this: this.map(|t| t.root(cx)),
50
+ func: func.root(cx),
51
+ func_name: func
52
+ .to_string(cx)
53
+ .map_or_else(|_| "anonymous func".to_owned(), |s| s.value(cx)),
54
+ chan: cx.channel(),
55
+ _marker: PhantomData,
56
+ }),
57
+ }
58
+ }
59
+
60
+ /// Synchronously call the callback from the current JS thread.
61
+ pub fn call<'cx, C: Context<'cx>>(&self, cx: &mut C, args: Args) -> BridgeResult<Ret> {
62
+ let inner = self.inner.clone();
63
+ inner.call(cx, args)
64
+ }
65
+
66
+ /// Call the callback on the JS thread and return a handle to the result.
67
+ pub fn call_on_js_thread(&self, args: Args) -> BridgeResult<neon::event::JoinHandle<Ret>> {
68
+ let inner = self.inner.clone();
69
+
70
+ let ret = inner
71
+ .chan
72
+ .clone()
73
+ .try_send(move |mut cx| inner.call(&mut cx, args).into_throw(&mut cx))
74
+ .map_err(|e| BridgeError::Other(e.into()))?;
75
+
76
+ Ok(ret)
77
+ }
78
+
79
+ pub fn call_and_block(&self, args: Args) -> BridgeResult<Ret> {
80
+ let join_handle = self.call_on_js_thread(args)?;
81
+
82
+ // This is... unfortunate but since this method is called from an async context way up
83
+ // the stack, but is not async itself AND we need some way to get the result from the JS
84
+ // callback, we must use this roundabout way of blocking. Simply calling `join` on the
85
+ // channel send won't work - it'll panic because it calls block_on internally.
86
+ let callback_res = futures::executor::block_on(join_handle);
87
+
88
+ match callback_res {
89
+ Ok(x) => Ok(x),
90
+ Err(e) => Err(BridgeError::Other(e.into())),
91
+ }
92
+ }
93
+ }
94
+
95
+ /// Unfortunately, the `TryFromJS` trait doesn't have access to the containing object, and therefore
96
+ /// can't preserve the `this` context. If the JS side API presents the callback as a method on some
97
+ /// object, then the function should be `bind(this)`'d on the JS side before passing it to Rust.
98
+ impl<Args, Ret> TryFromJs for JsCallback<Args, Ret>
99
+ where
100
+ Args: TryIntoJsArgs + Send + Sync + 'static,
101
+ Ret: TryFromJs + Send + Sync + 'static,
102
+ {
103
+ fn try_from_js<'cx, 'b>(
104
+ cx: &mut impl Context<'cx>,
105
+ js_value: Handle<'b, JsValue>,
106
+ ) -> BridgeResult<Self> {
107
+ let func = js_value.downcast::<JsFunction, _>(cx)?;
108
+ Ok(Self::new(cx, func, None))
109
+ }
110
+ }
111
+
112
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
113
+
114
+ pub struct JsAsyncCallback<Args, Ret>
115
+ where
116
+ Args: TryIntoJsArgs + Send + Sync,
117
+ Ret: TryFromJs + Send + Sync + 'static,
118
+ {
119
+ inner: Arc<CallbackInner<Args, JsFuture<BridgeResult<Ret>>>>,
120
+ }
121
+
122
+ impl<Args, Ret> JsAsyncCallback<Args, Ret>
123
+ where
124
+ Args: TryIntoJsArgs + Send + Sync + 'static,
125
+ Ret: TryFromJs + Send + Sync + 'static,
126
+ {
127
+ pub fn new<'cx, C: Context<'cx>>(
128
+ cx: &mut C,
129
+ func: Handle<JsFunction>,
130
+ this: Option<Handle<JsObject>>,
131
+ ) -> Self {
132
+ Self {
133
+ inner: Arc::new(CallbackInner {
134
+ this: this.map(|t| t.root(cx)),
135
+ func: func.root(cx),
136
+ func_name: func
137
+ .to_string(cx)
138
+ .map_or_else(|_| "anonymous func".to_owned(), |s| s.value(cx)),
139
+ chan: cx.channel(),
140
+ _marker: PhantomData,
141
+ }),
142
+ }
143
+ }
144
+
145
+ pub async fn call(&self, args: Args) -> BridgeResult<Ret> {
146
+ let inner = self.inner.clone();
147
+
148
+ let join_handle = inner
149
+ .chan
150
+ .clone()
151
+ .try_send(move |mut cx| inner.call(&mut cx, args).into_throw(&mut cx))
152
+ .map_err(|e| BridgeError::Other(e.into()))?;
153
+
154
+ // Wait for the JS function to return a Promise...
155
+ let res = join_handle.await;
156
+ let future = res.map_err(|e| BridgeError::Other(e.into()))?;
157
+
158
+ // ... and then wait for the Promise to resolve
159
+ let res = future.await;
160
+ let res = res.map_err(|e| BridgeError::Other(e.into()))??;
161
+ Ok(res)
162
+ }
163
+ }
164
+
165
+ /// Unfortunately, the `TryFromJS` trait doesn't have access to the containing object, and therefore
166
+ /// can't preserve the `this` context. If the JS side API presents the callback as a method on some
167
+ /// object, then the function should be `bind(this)`'d on the JS side before passing it to Rust.
168
+ impl<Args, Ret> TryFromJs for JsAsyncCallback<Args, Ret>
169
+ where
170
+ Args: TryIntoJsArgs + Send + Sync + 'static,
171
+ Ret: TryFromJs + Send + Sync + 'static,
172
+ {
173
+ fn try_from_js<'cx, 'b>(
174
+ cx: &mut impl Context<'cx>,
175
+ js_value: Handle<'b, JsValue>,
176
+ ) -> BridgeResult<Self> {
177
+ let func = js_value.downcast::<JsFunction, _>(cx)?;
178
+ Ok(Self::new(cx, func, None))
179
+ }
180
+ }
181
+
182
+ impl<R: TryFromJs + Send + Sync + 'static> TryFromJs for JsFuture<BridgeResult<R>> {
183
+ fn try_from_js<'cx, 'b>(
184
+ cx: &mut impl Context<'cx>,
185
+ js_value: Handle<'b, JsValue>,
186
+ ) -> BridgeResult<Self> {
187
+ let promise = js_value.downcast::<JsPromise, _>(cx)?;
188
+ let future = promise.to_future(cx, |mut cx, result| match result {
189
+ Ok(value) => {
190
+ // Promise resolved, but there might still be an error trying to
191
+ // convert the promise's returned value to the desired type.
192
+ let val = R::try_from_js(&mut cx, value);
193
+ match val {
194
+ Ok(val) => Ok(Ok(val)),
195
+ Err(e) => Ok(Err(e)),
196
+ }
197
+ }
198
+ Err(e) => {
199
+ // Promise failed to resolve
200
+ let err_str = e.to_string(&mut cx)?.value(&mut cx);
201
+ Ok(Err(BridgeError::UnexpectedError(err_str)))
202
+ }
203
+ })?;
204
+
205
+ Ok(future)
206
+ }
207
+ }
208
+
209
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
210
+
211
+ #[derive(Debug)]
212
+ struct CallbackInner<Args, Ret>
213
+ where
214
+ Args: TryIntoJsArgs + Send + Sync,
215
+ Ret: TryFromJs + Send + Sync,
216
+ {
217
+ this: Option<Root<JsObject>>,
218
+ func: Root<JsFunction>,
219
+ func_name: String,
220
+ chan: Channel,
221
+ _marker: PhantomData<(Args, Ret)>,
222
+ }
223
+
224
+ impl<Args: TryIntoJsArgs + Send + Sync, Ret: TryFromJs + Send + Sync> CallbackInner<Args, Ret> {
225
+ fn call<'a, C: Context<'a>>(&self, cx: &mut C, args: Args) -> BridgeResult<Ret> {
226
+ let this: Handle<'a, JsValue> = self
227
+ .this
228
+ .as_ref()
229
+ .map_or(cx.undefined().upcast(), |t| t.to_inner(cx).upcast());
230
+
231
+ // Convert the arguments to a JS array using the new trait
232
+ let js_args = args.try_into_js_args(cx)?;
233
+
234
+ // Call the function with the JS arguments directly
235
+ let ret = cx
236
+ .try_catch(|cx| self.func.to_inner(cx).call(cx, this, js_args))
237
+ .map_err(|e| {
238
+ let err_str = format!(
239
+ "Error calling JS callback '{}' on JS thread, {:?}",
240
+ self.func_name, e
241
+ );
242
+ error!(err_str);
243
+ BridgeError::UnexpectedError(err_str)
244
+ })?;
245
+
246
+ <Ret>::try_from_js(cx, ret)
247
+ }
248
+ }
249
+
250
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
251
+
252
+ /// A trait to build an arguments array for a JS function call from a tuple.
253
+ pub trait TryIntoJsArgs {
254
+ fn try_into_js_args<'cx, 'a>(
255
+ self,
256
+ cx: &mut impl Context<'cx>,
257
+ ) -> BridgeResult<Vec<Handle<'a, JsValue>>>
258
+ where
259
+ 'cx: 'a;
260
+ }
261
+
262
+ impl TryIntoJsArgs for () {
263
+ fn try_into_js_args<'cx, 'a>(
264
+ self,
265
+ _cx: &mut impl Context<'cx>,
266
+ ) -> BridgeResult<Vec<Handle<'a, JsValue>>>
267
+ where
268
+ 'cx: 'a,
269
+ {
270
+ Ok(Vec::new())
271
+ }
272
+ }
273
+
274
+ impl<T0: TryIntoJs> TryIntoJsArgs for (T0,) {
275
+ fn try_into_js_args<'cx, 'a>(
276
+ self,
277
+ cx: &mut impl Context<'cx>,
278
+ ) -> BridgeResult<Vec<Handle<'a, JsValue>>>
279
+ where
280
+ 'cx: 'a,
281
+ {
282
+ let js_value = self.0.try_into_js(cx)?;
283
+ Ok(vec![js_value.upcast()])
284
+ }
285
+ }
286
+
287
+ impl<T0: TryIntoJs, T1: TryIntoJs> TryIntoJsArgs for (T0, T1) {
288
+ fn try_into_js_args<'cx, 'a>(
289
+ self,
290
+ cx: &mut impl Context<'cx>,
291
+ ) -> BridgeResult<Vec<Handle<'a, JsValue>>>
292
+ where
293
+ 'cx: 'a,
294
+ {
295
+ let v0 = self.0.try_into_js(cx)?;
296
+ let v1 = self.1.try_into_js(cx)?;
297
+ Ok(vec![v0.upcast(), v1.upcast()])
298
+ }
299
+ }