@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
package/src/worker.rs CHANGED
@@ -1,454 +1,983 @@
1
- use crate::{conversions::ObjectHandleConversionsExt, errors::*, helpers::*, runtime::*};
2
- use futures::stream::StreamExt;
3
- use neon::{prelude::*, types::buffer::TypedArray};
1
+ use std::sync::Arc;
2
+
3
+ use anyhow::Context as AnyhowContext;
4
+ use neon::prelude::*;
4
5
  use prost::Message;
5
- use std::{cell::RefCell, sync::Arc};
6
- use temporal_sdk_core::replay::HistoryForReplay;
6
+ use tokio::sync::mpsc::{Sender, channel};
7
+ use tokio_stream::wrappers::ReceiverStream;
8
+
7
9
  use temporal_sdk_core::{
10
+ CoreRuntime,
8
11
  api::{
9
- errors::{CompleteActivityError, CompleteWfError, PollError},
10
12
  Worker as CoreWorkerTrait,
13
+ errors::{CompleteActivityError, CompleteWfError, PollError},
11
14
  },
15
+ init_replay_worker, init_worker,
12
16
  protos::{
13
17
  coresdk::{
14
- workflow_completion::WorkflowActivationCompletion, ActivityHeartbeat,
15
- ActivityTaskCompletion,
18
+ ActivityHeartbeat, ActivityTaskCompletion,
19
+ workflow_completion::WorkflowActivationCompletion,
16
20
  },
17
21
  temporal::api::history::v1::History,
18
22
  },
19
- Worker as CoreWorker,
23
+ replay::{HistoryForReplay, ReplayWorkerInput},
20
24
  };
21
- use tokio::sync::mpsc::{unbounded_channel, UnboundedSender};
22
- use tokio_stream::wrappers::UnboundedReceiverStream;
23
25
 
24
- /// Worker struct, hold a reference for the channel sender responsible for sending requests from
25
- /// JS to a bridge thread which forwards them to core
26
- pub struct WorkerHandle {
27
- pub(crate) sender: UnboundedSender<WorkerRequest>,
26
+ use bridge_macros::js_function;
27
+
28
+ use crate::{
29
+ client::Client,
30
+ enter_sync,
31
+ helpers::{handles::MutableFinalize, *},
32
+ runtime::{Runtime, RuntimeExt},
33
+ };
34
+
35
+ pub fn init(cx: &mut ModuleContext) -> NeonResult<()> {
36
+ cx.export_function("newWorker", worker_new)?;
37
+ cx.export_function("workerValidate", worker_validate)?;
38
+
39
+ cx.export_function(
40
+ "workerPollWorkflowActivation",
41
+ worker_poll_workflow_activation,
42
+ )?;
43
+ cx.export_function(
44
+ "workerCompleteWorkflowActivation",
45
+ worker_complete_workflow_activation,
46
+ )?;
47
+
48
+ cx.export_function("workerPollActivityTask", worker_poll_activity_task)?;
49
+ cx.export_function("workerCompleteActivityTask", worker_complete_activity_task)?;
50
+ cx.export_function(
51
+ "workerRecordActivityHeartbeat",
52
+ worker_record_activity_heartbeat,
53
+ )?;
54
+
55
+ cx.export_function("workerInitiateShutdown", worker_initiate_shutdown)?;
56
+ cx.export_function("workerFinalizeShutdown", worker_finalize_shutdown)?;
57
+
58
+ // Replay worker functions
59
+ cx.export_function("newReplayWorker", replay_worker_new)?;
60
+ cx.export_function("pushHistory", push_history)?;
61
+ cx.export_function("closeHistoryStream", close_history_stream)?;
62
+
63
+ Ok(())
28
64
  }
29
65
 
30
- /// Box it so we can use Worker from JS
31
- pub type BoxedWorker = JsBox<RefCell<Option<WorkerHandle>>>;
32
- impl Finalize for WorkerHandle {}
33
-
34
- #[derive(Debug)]
35
- pub enum WorkerRequest {
36
- /// A request to shutdown a worker, the worker instance will remain active to
37
- /// allow draining of pending tasks
38
- InitiateShutdown {
39
- /// Used to send the result back into JS
40
- callback: Root<JsFunction>,
41
- },
42
- /// A request to poll for workflow activations
43
- PollWorkflowActivation {
44
- /// Used to send the result back into JS
45
- callback: Root<JsFunction>,
46
- },
47
- /// A request to complete a single workflow activation
48
- CompleteWorkflowActivation {
49
- completion: WorkflowActivationCompletion,
50
- /// Used to send the result back into JS
51
- callback: Root<JsFunction>,
52
- },
53
- /// A request to poll for activity tasks
54
- PollActivityTask {
55
- /// Used to report completion or error back into JS
56
- callback: Root<JsFunction>,
57
- },
58
- /// A request to complete a single activity task
59
- CompleteActivityTask {
60
- completion: ActivityTaskCompletion,
61
- /// Used to send the result back into JS
62
- callback: Root<JsFunction>,
63
- },
64
- /// A request to send a heartbeat from a running activity
65
- RecordActivityHeartbeat { heartbeat: ActivityHeartbeat },
66
+ pub struct Worker {
67
+ core_runtime: Arc<CoreRuntime>,
68
+
69
+ // Arc so that we can send reference into async closures
70
+ core_worker: Arc<temporal_sdk_core::Worker>,
71
+ }
72
+
73
+ /// Create a new worker.
74
+ #[js_function]
75
+ pub fn worker_new(
76
+ client: OpaqueInboundHandle<Client>,
77
+ worker_options: config::BridgeWorkerOptions,
78
+ ) -> BridgeResult<OpaqueOutboundHandle<Worker>> {
79
+ let config = worker_options
80
+ .into_core_config()
81
+ .context("Failed to convert WorkerOptions to CoreWorkerConfig")?;
82
+
83
+ let client_ref = client.borrow()?;
84
+ let client = client_ref.core_client.clone();
85
+ let runtime = client_ref.core_runtime.clone();
86
+
87
+ enter_sync!(runtime);
88
+ let worker = init_worker(&runtime, config, client).context("Failed to initialize worker")?;
89
+
90
+ Ok(OpaqueOutboundHandle::new(Worker {
91
+ core_runtime: runtime,
92
+ core_worker: Arc::new(worker),
93
+ }))
66
94
  }
67
95
 
68
- /// Polls on [WorkerRequest]s via given channel.
69
- /// Bridges requests from JS to core and sends responses back to JS using a neon::Channel.
70
- /// Returns when the given channel is dropped.
71
- pub async fn start_worker_loop(
72
- worker: CoreWorker,
73
- channel: Arc<Channel>,
74
- callback: Root<JsFunction>,
75
- is_replay: Option<HistoryForReplayTunnel>,
76
- ) {
77
- if is_replay.is_none() {
78
- if let Err(e) = worker.validate().await {
79
- send_error(channel, callback, move |cx| {
80
- make_named_error_from_error(cx, TRANSPORT_ERROR, e)
81
- });
82
- return;
96
+ /// Validate a worker.
97
+ #[js_function]
98
+ pub fn worker_validate(worker: OpaqueInboundHandle<Worker>) -> BridgeResult<BridgeFuture<()>> {
99
+ let worker_ref = worker.borrow()?;
100
+ let worker = worker_ref.core_worker.clone();
101
+ let runtime = worker_ref.core_runtime.clone();
102
+
103
+ runtime.future_to_promise(async move {
104
+ worker
105
+ .validate()
106
+ .await
107
+ .map_err(|err| BridgeError::TransportError(err.to_string()))
108
+ })
109
+ }
110
+
111
+ /// Initiate a single workflow activation poll request.
112
+ /// There should be only one concurrent poll request for this type.
113
+ #[js_function]
114
+ pub fn worker_poll_workflow_activation(
115
+ worker: OpaqueInboundHandle<Worker>,
116
+ ) -> BridgeResult<BridgeFuture<Vec<u8>>> {
117
+ let worker_ref = worker.borrow()?;
118
+ let worker = worker_ref.core_worker.clone();
119
+ let runtime = worker_ref.core_runtime.clone();
120
+
121
+ runtime.future_to_promise(async move {
122
+ let result = worker.poll_workflow_activation().await;
123
+
124
+ match result {
125
+ Ok(task) => Ok(task.encode_to_vec()),
126
+ Err(err) => match err {
127
+ PollError::ShutDown => Err(BridgeError::WorkerShutdown)?,
128
+ PollError::TonicError(status) => {
129
+ Err(BridgeError::TransportError(status.message().to_string()))?
130
+ }
131
+ },
83
132
  }
84
- }
85
- let (tx, rx) = unbounded_channel();
86
- // Return the worker after validation has happened
87
- if let Some(tunnel) = is_replay {
88
- send_result(channel.clone(), callback, |cx| {
89
- let worker = cx.boxed(RefCell::new(Some(WorkerHandle { sender: tx })));
90
- let tunnel = cx.boxed(tunnel);
91
- let retme = cx.empty_object();
92
- retme.set(cx, "worker", worker)?;
93
- retme.set(cx, "pusher", tunnel)?;
94
- Ok(retme)
95
- })
96
- } else {
97
- send_result(channel.clone(), callback, |cx| {
98
- Ok(cx.boxed(RefCell::new(Some(WorkerHandle { sender: tx }))))
99
- });
100
- }
101
- UnboundedReceiverStream::new(rx)
102
- .for_each_concurrent(None, |request| {
103
- let worker = &worker;
104
- let channel = channel.clone();
105
- async move {
106
- match request {
107
- WorkerRequest::InitiateShutdown { callback } => {
108
- worker.initiate_shutdown();
109
- send_result(channel, callback, |cx| Ok(cx.undefined()));
110
- }
111
- WorkerRequest::PollWorkflowActivation { callback } => {
112
- handle_poll_workflow_activation_request(worker, channel, callback).await
113
- }
114
- WorkerRequest::PollActivityTask { callback } => {
115
- handle_poll_activity_task_request(worker, channel, callback).await
116
- }
117
- WorkerRequest::CompleteWorkflowActivation {
118
- completion,
119
- callback,
120
- } => {
121
- void_future_to_js(
122
- channel,
123
- callback,
124
- async move { worker.complete_workflow_activation(completion).await },
125
- |cx, err| -> JsResult<JsObject> {
126
- match err {
127
- CompleteWfError::MalformedWorkflowCompletion {
128
- reason, ..
129
- } => Ok(JsError::type_error(cx, reason)?.upcast()),
130
- }
131
- },
132
- )
133
- .await;
134
- }
135
- WorkerRequest::CompleteActivityTask {
136
- completion,
137
- callback,
138
- } => {
139
- void_future_to_js(
140
- channel,
141
- callback,
142
- async move { worker.complete_activity_task(completion).await },
143
- |cx, err| -> JsResult<JsObject> {
144
- match err {
145
- CompleteActivityError::MalformedActivityCompletion {
146
- reason,
147
- ..
148
- } => Ok(JsError::type_error(cx, reason)?.upcast()),
149
- }
150
- },
151
- )
152
- .await;
153
- }
154
- WorkerRequest::RecordActivityHeartbeat { heartbeat } => {
155
- worker.record_activity_heartbeat(heartbeat)
133
+ })
134
+ }
135
+
136
+ /// Submit a workflow activation completion to core.
137
+ #[js_function]
138
+ pub fn worker_complete_workflow_activation(
139
+ worker: OpaqueInboundHandle<Worker>,
140
+ completion: Vec<u8>,
141
+ ) -> BridgeResult<BridgeFuture<()>> {
142
+ let workflow_completion = WorkflowActivationCompletion::decode_length_delimited(
143
+ completion.as_slice(),
144
+ )
145
+ .map_err(|err| BridgeError::TypeError {
146
+ field: None,
147
+ message: format!("Cannot decode Completion from buffer: {err:?}"),
148
+ })?;
149
+
150
+ let worker_ref = worker.borrow()?;
151
+ let worker = worker_ref.core_worker.clone();
152
+ let runtime = worker_ref.core_runtime.clone();
153
+
154
+ runtime.future_to_promise(async move {
155
+ worker
156
+ .complete_workflow_activation(workflow_completion)
157
+ .await
158
+ .map_err(|err| match err {
159
+ CompleteWfError::MalformedWorkflowCompletion { reason, run_id } => {
160
+ BridgeError::TypeError {
161
+ field: None,
162
+ message: format!(
163
+ "Malformed Workflow Completion: {reason:?} for RunID={run_id}"
164
+ ),
156
165
  }
157
166
  }
158
- }
159
- })
160
- .await;
161
- worker.finalize_shutdown().await;
167
+ })
168
+ })
162
169
  }
163
170
 
164
- /// Called within the poll loop thread, calls core and triggers JS callback with result
165
- async fn handle_poll_workflow_activation_request(
166
- worker: &CoreWorker,
167
- channel: Arc<Channel>,
168
- callback: Root<JsFunction>,
169
- ) {
170
- match worker.poll_workflow_activation().await {
171
- Ok(task) => {
172
- send_result(channel, callback, move |cx| {
173
- let len = task.encoded_len();
174
- let mut result = JsArrayBuffer::new(cx, len)?;
175
- let mut slice = result.as_mut_slice(cx);
176
- if task.encode(&mut slice).is_err() {
177
- panic!("Failed to encode task")
178
- };
179
- Ok(result)
180
- });
181
- }
182
- Err(err) => {
183
- send_error(channel, callback, move |cx| match err {
184
- PollError::ShutDown => make_named_error_from_error(cx, SHUTDOWN_ERROR, err),
185
- PollError::TonicError(_) => make_named_error_from_error(cx, TRANSPORT_ERROR, err),
186
- });
171
+ /// Initiate a single activity task poll request.
172
+ /// There should be only one concurrent poll request for this type.
173
+ #[js_function]
174
+ pub fn worker_poll_activity_task(
175
+ worker: OpaqueInboundHandle<Worker>,
176
+ ) -> BridgeResult<BridgeFuture<Vec<u8>>> {
177
+ let worker_ref = worker.borrow()?;
178
+ let worker = worker_ref.core_worker.clone();
179
+ let runtime = worker_ref.core_runtime.clone();
180
+
181
+ runtime.future_to_promise(async move {
182
+ let result = worker.poll_activity_task().await;
183
+
184
+ match result {
185
+ Ok(task) => Ok(task.encode_to_vec()),
186
+ Err(err) => match err {
187
+ PollError::ShutDown => Err(BridgeError::WorkerShutdown)?,
188
+ PollError::TonicError(status) => {
189
+ Err(BridgeError::TransportError(status.message().to_string()))?
190
+ }
191
+ },
187
192
  }
188
- }
193
+ })
189
194
  }
190
195
 
191
- /// Called within the poll loop thread, calls core and triggers JS callback with result
192
- pub async fn handle_poll_activity_task_request(
193
- worker: &CoreWorker,
194
- channel: Arc<Channel>,
195
- callback: Root<JsFunction>,
196
- ) {
197
- match worker.poll_activity_task().await {
198
- Ok(task) => {
199
- send_result(channel, callback, move |cx| {
200
- let len = task.encoded_len();
201
- let mut result = JsArrayBuffer::new(cx, len)?;
202
- let mut slice = result.as_mut_slice(cx);
203
- if task.encode(&mut slice).is_err() {
204
- panic!("Failed to encode task")
205
- };
206
- Ok(result)
207
- });
208
- }
209
- Err(err) => {
210
- send_error(channel, callback, move |cx| match err {
211
- PollError::ShutDown => make_named_error_from_error(cx, SHUTDOWN_ERROR, err),
212
- PollError::TonicError(_) => make_named_error_from_error(cx, TRANSPORT_ERROR, err),
213
- });
196
+ /// Submit an activity task completion to core.
197
+ #[js_function]
198
+ pub fn worker_complete_activity_task(
199
+ worker: OpaqueInboundHandle<Worker>,
200
+ completion: Vec<u8>,
201
+ ) -> BridgeResult<BridgeFuture<()>> {
202
+ let activity_completion =
203
+ ActivityTaskCompletion::decode_length_delimited(completion.as_slice()).map_err(|err| {
204
+ BridgeError::TypeError {
205
+ field: None,
206
+ message: format!("Cannot decode Completion from buffer: {err:?}"),
207
+ }
208
+ })?;
209
+
210
+ let worker_ref = worker.borrow()?;
211
+ let worker = worker_ref.core_worker.clone();
212
+ let runtime = worker_ref.core_runtime.clone();
213
+
214
+ runtime.future_to_promise(async move {
215
+ worker
216
+ .complete_activity_task(activity_completion)
217
+ .await
218
+ .map_err(|err| match err {
219
+ CompleteActivityError::MalformedActivityCompletion {
220
+ reason,
221
+ completion: _,
222
+ } => BridgeError::TypeError {
223
+ field: None,
224
+ message: format!("Malformed Activity Completion: {reason:?}"),
225
+ },
226
+ })
227
+ })
228
+ }
229
+
230
+ /// Submit an activity heartbeat to core.
231
+ #[js_function]
232
+ pub fn worker_record_activity_heartbeat(
233
+ worker: OpaqueInboundHandle<Worker>,
234
+ heartbeat: Vec<u8>,
235
+ ) -> BridgeResult<()> {
236
+ let activity_heartbeat = ActivityHeartbeat::decode_length_delimited(heartbeat.as_slice())
237
+ .map_err(|err| BridgeError::TypeError {
238
+ field: None,
239
+ message: format!("Cannot decode Heartbeat from buffer: {err:?}"),
240
+ })?;
241
+
242
+ let worker_ref = worker.borrow()?;
243
+ worker_ref
244
+ .core_worker
245
+ .record_activity_heartbeat(activity_heartbeat);
246
+
247
+ Ok(())
248
+ }
249
+
250
+ /// Request shutdown of the worker.
251
+ /// Once complete Core will stop polling on new tasks and activations on worker's task queue.
252
+ /// Caller should drain any pending tasks and activations and call worker_finalize_shutdown before breaking from
253
+ /// the loop to ensure graceful shutdown.
254
+ #[js_function]
255
+ pub fn worker_initiate_shutdown(worker: OpaqueInboundHandle<Worker>) -> BridgeResult<()> {
256
+ let worker_ref = worker.borrow()?;
257
+ worker_ref.core_worker.initiate_shutdown();
258
+ Ok(())
259
+ }
260
+
261
+ #[js_function]
262
+ pub fn worker_finalize_shutdown(
263
+ worker: OpaqueInboundHandle<Worker>,
264
+ ) -> BridgeResult<BridgeFuture<()>> {
265
+ let worker_ref = worker.take()?;
266
+
267
+ // We make supplementary copies of this arc in `worker_poll_*_task`, `worker_complete_*_task`, and
268
+ // similar functions. Lang is responsible to ensure that there is no outstanding calls to these
269
+ // functions by the time `finalize_shutdown` is called, in which case unwrapping the arc here is ok.
270
+ let worker = Arc::try_unwrap(worker_ref.core_worker).map_err(|arc| {
271
+ BridgeError::IllegalStateStillInUse {
272
+ what: "Worker",
273
+ details: Some(format!(
274
+ "Expected 1 reference, but got {}",
275
+ Arc::strong_count(&arc)
276
+ )),
214
277
  }
278
+ })?;
279
+
280
+ worker_ref.core_runtime.future_to_promise(async move {
281
+ worker.finalize_shutdown().await;
282
+ Ok(())
283
+ })
284
+ }
285
+
286
+ impl MutableFinalize for Worker {
287
+ fn finalize_mut(self) {
288
+ self.core_runtime.spawn_and_forget(async move {
289
+ // We make supplementary copies of this arc in `worker_poll_*_task`, `worker_complete_*_task`, and
290
+ // similar functions. Lang is responsible to ensure that there is no outstanding calls to these
291
+ // functions by the time `finalize_shutdown` is called, in which case unwrapping the arc here is ok.
292
+ let worker = Arc::try_unwrap(self.core_worker).map_err(|arc| {
293
+ BridgeError::IllegalStateStillInUse {
294
+ what: "Worker",
295
+ details: Some(format!(
296
+ "Expected 1 reference, but got {}",
297
+ Arc::strong_count(&arc)
298
+ )),
299
+ }
300
+ })?;
301
+
302
+ worker.finalize_shutdown().await;
303
+ Ok(())
304
+ });
215
305
  }
216
306
  }
217
307
 
218
- // Below are functions exported to JS
308
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
219
309
 
220
- /// Create a new worker asynchronously.
221
- /// Worker uses the provided connection and returned to JS using supplied `callback`.
222
- pub fn worker_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
223
- let client = cx.argument::<BoxedClient>(0)?;
224
- let worker_options = cx.argument::<JsObject>(1)?;
225
- let callback = cx.argument::<JsFunction>(2)?;
310
+ pub struct HistoryForReplayTunnelHandle {
311
+ core_runtime: Arc<CoreRuntime>,
312
+ sender: Sender<HistoryForReplay>,
313
+ }
226
314
 
227
- match client.borrow().as_ref() {
228
- None => {
229
- callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Client")?;
230
- }
231
- Some(client) => {
232
- let config = worker_options.as_worker_config(&mut cx)?;
233
- let request = RuntimeRequest::InitWorker {
234
- client: client.core_client.clone(),
235
- config,
236
- callback: callback.root(&mut cx),
237
- };
238
- if let Err(err) = client.runtime.sender.send(request) {
239
- callback_with_unexpected_error(&mut cx, callback, err)?;
240
- };
241
- }
242
- };
315
+ impl HistoryForReplayTunnelHandle {
316
+ fn new(runtime: &Arc<CoreRuntime>) -> (Self, ReceiverStream<HistoryForReplay>) {
317
+ let (sender, rx) = channel(1);
318
+ (
319
+ Self {
320
+ core_runtime: Arc::clone(runtime),
321
+ sender,
322
+ },
323
+ ReceiverStream::new(rx),
324
+ )
325
+ }
243
326
 
244
- Ok(cx.undefined())
327
+ pub(crate) fn get_chan(&self) -> Sender<HistoryForReplay> {
328
+ self.sender.clone()
329
+ }
245
330
  }
246
331
 
332
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
333
+
247
334
  /// Create a new replay worker asynchronously.
248
- /// Worker is returned to JS using supplied callback.
249
- pub fn replay_worker_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
250
- let runtime = cx.argument::<BoxedRuntime>(0)?;
251
- let worker_options = cx.argument::<JsObject>(1)?;
252
- let callback = cx.argument::<JsFunction>(2)?;
253
-
254
- let config = worker_options.as_worker_config(&mut cx)?;
255
- let request = RuntimeRequest::InitReplayWorker {
256
- runtime: (*runtime).clone(),
257
- config,
258
- callback: callback.root(&mut cx),
259
- };
260
- if let Err(err) = runtime.sender.send(request) {
261
- callback_with_unexpected_error(&mut cx, callback, err)?;
335
+ #[js_function]
336
+ pub fn replay_worker_new(
337
+ runtime: OpaqueInboundHandle<Runtime>,
338
+ config: config::BridgeWorkerOptions,
339
+ ) -> BridgeResult<(
340
+ OpaqueOutboundHandle<Worker>,
341
+ OpaqueOutboundHandle<HistoryForReplayTunnelHandle>,
342
+ )> {
343
+ let config = config
344
+ .into_core_config()
345
+ .context("Failed to convert WorkerOptions to CoreWorkerConfig")?;
346
+
347
+ let runtime = runtime.borrow()?.core_runtime.clone();
348
+ enter_sync!(runtime);
349
+
350
+ let (tunnel, stream) = HistoryForReplayTunnelHandle::new(&runtime);
351
+
352
+ let worker = init_replay_worker(ReplayWorkerInput::new(config, Box::pin(stream)))
353
+ .context("Failed to initialize replay worker")?;
354
+
355
+ let worker_handle = Worker {
356
+ core_runtime: runtime,
357
+ core_worker: Arc::new(worker),
262
358
  };
263
359
 
264
- Ok(cx.undefined())
360
+ Ok((
361
+ OpaqueOutboundHandle::new(worker_handle),
362
+ OpaqueOutboundHandle::new(tunnel),
363
+ ))
265
364
  }
266
365
 
267
- pub fn push_history(mut cx: FunctionContext) -> JsResult<JsUndefined> {
268
- let pusher = cx.argument::<JsBox<HistoryForReplayTunnel>>(0)?;
269
- let workflow_id = cx.argument::<JsString>(1)?;
270
- let history_binary = cx.argument::<JsArrayBuffer>(2)?;
271
- let callback = cx.argument::<JsFunction>(3)?;
272
- let data = history_binary.as_slice(&cx);
273
- match History::decode_length_delimited(data) {
274
- Ok(hist) => {
275
- let workflow_id = workflow_id.value(&mut cx);
276
- if let Err(e) = pusher.get_chan().map(|chan| {
277
- pusher
278
- .runtime
279
- .sender
280
- .send(RuntimeRequest::PushReplayHistory {
281
- tx: chan,
282
- pushme: HistoryForReplay::new(hist, workflow_id),
283
- callback: callback.root(&mut cx),
284
- })
285
- }) {
286
- callback_with_unexpected_error(&mut cx, callback, e)?;
366
+ #[js_function]
367
+ pub fn push_history(
368
+ pusher: OpaqueInboundHandle<HistoryForReplayTunnelHandle>,
369
+ workflow_id: String,
370
+ history_binary: Vec<u8>,
371
+ ) -> BridgeResult<BridgeFuture<()>> {
372
+ let history: History =
373
+ History::decode_length_delimited(history_binary.as_slice()).map_err(|err| {
374
+ BridgeError::TypeError {
375
+ field: None,
376
+ message: format!("Cannot decode History from buffer: {err:?}"),
287
377
  }
288
- Ok(cx.undefined())
289
- }
290
- Err(e) => cx.throw_error(format!("Error decoding history: {:?}", e)),
291
- }
378
+ })?;
379
+ let history = HistoryForReplay::new(history, workflow_id);
380
+
381
+ let pusher_ref = pusher.borrow()?;
382
+ let chan = pusher_ref.get_chan();
383
+
384
+ pusher_ref.core_runtime.future_to_promise(async move {
385
+ chan.send(history)
386
+ .await
387
+ .context("Error pushing history to replay worker")?;
388
+ Ok(())
389
+ })
292
390
  }
293
391
 
294
- pub fn close_history_stream(mut cx: FunctionContext) -> JsResult<JsUndefined> {
295
- let pusher = cx.argument::<JsBox<HistoryForReplayTunnel>>(0)?;
296
- pusher.shutdown();
297
- Ok(cx.undefined())
392
+ #[js_function]
393
+ pub fn close_history_stream(
394
+ pusher: OpaqueInboundHandle<HistoryForReplayTunnelHandle>,
395
+ ) -> BridgeResult<()> {
396
+ // Just drop the pusher's channel; there's actually no "close" method on the channel.
397
+ let _pusher_ref = pusher.take()?;
398
+ Ok(())
298
399
  }
299
400
 
300
- /// Initiate a single workflow activation poll request.
301
- /// There should be only one concurrent poll request for this type.
302
- pub fn worker_poll_workflow_activation(mut cx: FunctionContext) -> JsResult<JsUndefined> {
303
- let worker = cx.argument::<BoxedWorker>(0)?;
304
- let callback = cx.argument::<JsFunction>(1)?;
305
- match worker.borrow().as_ref() {
306
- None => {
307
- callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Worker")?;
401
+ /// Let drop handle the cleanup.
402
+ impl MutableFinalize for HistoryForReplayTunnelHandle {}
403
+
404
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
405
+
406
+ mod config {
407
+ use std::{sync::Arc, time::Duration};
408
+
409
+ use temporal_sdk_core::{
410
+ ResourceBasedSlotsOptions, ResourceBasedSlotsOptionsBuilder, ResourceSlotOptions,
411
+ SlotSupplierOptions as CoreSlotSupplierOptions, TunerHolder, TunerHolderOptionsBuilder,
412
+ api::worker::{
413
+ ActivitySlotKind, LocalActivitySlotKind, PollerBehavior as CorePollerBehavior,
414
+ SlotKind, WorkerConfig, WorkerConfigBuilder, WorkerConfigBuilderError,
415
+ WorkerDeploymentOptions as CoreWorkerDeploymentOptions,
416
+ WorkerDeploymentVersion as CoreWorkerDeploymentVersion, WorkflowSlotKind,
417
+ },
418
+ protos::temporal::api::enums::v1::VersioningBehavior as CoreVersioningBehavior,
419
+ };
420
+
421
+ use super::custom_slot_supplier::CustomSlotSupplierOptions;
422
+ use crate::helpers::TryIntoJs;
423
+ use bridge_macros::TryFromJs;
424
+ use neon::context::Context;
425
+ use neon::object::Object;
426
+ use neon::prelude::JsResult;
427
+ use neon::types::JsObject;
428
+ use temporal_sdk_core::api::worker::WorkerVersioningStrategy;
429
+
430
+ #[derive(TryFromJs)]
431
+ pub struct BridgeWorkerOptions {
432
+ identity: String,
433
+ build_id: String,
434
+ use_versioning: bool,
435
+ worker_deployment_options: Option<WorkerDeploymentOptions>,
436
+ task_queue: String,
437
+ namespace: String,
438
+ tuner: WorkerTuner,
439
+ non_sticky_to_sticky_poll_ratio: f32,
440
+ workflow_task_poller_behavior: PollerBehavior,
441
+ activity_task_poller_behavior: PollerBehavior,
442
+ enable_non_local_activities: bool,
443
+ sticky_queue_schedule_to_start_timeout: Duration,
444
+ max_cached_workflows: usize,
445
+ max_heartbeat_throttle_interval: Duration,
446
+ default_heartbeat_throttle_interval: Duration,
447
+ max_activities_per_second: Option<f64>,
448
+ max_task_queue_activities_per_second: Option<f64>,
449
+ shutdown_grace_time: Option<Duration>,
450
+ }
451
+
452
+ #[derive(TryFromJs)]
453
+ pub enum PollerBehavior {
454
+ SimpleMaximum {
455
+ maximum: usize,
456
+ },
457
+ Autoscaling {
458
+ minimum: usize,
459
+ maximum: usize,
460
+ initial: usize,
461
+ },
462
+ }
463
+
464
+ #[derive(TryFromJs)]
465
+ pub struct WorkerDeploymentOptions {
466
+ version: WorkerDeploymentVersion,
467
+ use_worker_versioning: bool,
468
+ default_versioning_behavior: VersioningBehavior,
469
+ }
470
+
471
+ #[derive(TryFromJs)]
472
+ pub struct WorkerDeploymentVersion {
473
+ build_id: String,
474
+ deployment_name: String,
475
+ }
476
+
477
+ #[derive(TryFromJs)]
478
+ pub enum VersioningBehavior {
479
+ Pinned,
480
+ AutoUpgrade,
481
+ }
482
+
483
+ impl BridgeWorkerOptions {
484
+ pub(crate) fn into_core_config(self) -> Result<WorkerConfig, WorkerConfigBuilderError> {
485
+ // Set all other options
486
+ let mut builder = WorkerConfigBuilder::default();
487
+ builder
488
+ .client_identity_override(Some(self.identity))
489
+ .versioning_strategy({
490
+ if let Some(dopts) = self.worker_deployment_options {
491
+ WorkerVersioningStrategy::WorkerDeploymentBased(dopts.into())
492
+ } else if self.use_versioning {
493
+ WorkerVersioningStrategy::LegacyBuildIdBased {
494
+ build_id: self.build_id,
495
+ }
496
+ } else {
497
+ WorkerVersioningStrategy::None {
498
+ build_id: self.build_id,
499
+ }
500
+ }
501
+ })
502
+ .task_queue(self.task_queue)
503
+ .namespace(self.namespace)
504
+ .tuner(self.tuner.into_core_config()?)
505
+ .nonsticky_to_sticky_poll_ratio(self.non_sticky_to_sticky_poll_ratio)
506
+ .workflow_task_poller_behavior(self.workflow_task_poller_behavior)
507
+ .activity_task_poller_behavior(self.activity_task_poller_behavior)
508
+ .no_remote_activities(!self.enable_non_local_activities)
509
+ .sticky_queue_schedule_to_start_timeout(self.sticky_queue_schedule_to_start_timeout)
510
+ .max_cached_workflows(self.max_cached_workflows)
511
+ .max_heartbeat_throttle_interval(self.max_heartbeat_throttle_interval)
512
+ .default_heartbeat_throttle_interval(self.default_heartbeat_throttle_interval)
513
+ .max_task_queue_activities_per_second(self.max_task_queue_activities_per_second)
514
+ .max_worker_activities_per_second(self.max_activities_per_second)
515
+ .graceful_shutdown_period(self.shutdown_grace_time)
516
+ .build()
308
517
  }
309
- Some(worker) => {
310
- let request = WorkerRequest::PollWorkflowActivation {
311
- callback: callback.root(&mut cx),
312
- };
313
- if let Err(err) = worker.sender.send(request) {
314
- callback_with_unexpected_error(&mut cx, callback, err)?;
518
+ }
519
+
520
+ impl From<PollerBehavior> for CorePollerBehavior {
521
+ fn from(val: PollerBehavior) -> Self {
522
+ match val {
523
+ PollerBehavior::SimpleMaximum { maximum } => Self::SimpleMaximum(maximum),
524
+ PollerBehavior::Autoscaling {
525
+ minimum,
526
+ maximum,
527
+ initial,
528
+ } => Self::Autoscaling {
529
+ minimum,
530
+ maximum,
531
+ initial,
532
+ },
315
533
  }
316
534
  }
317
535
  }
318
- Ok(cx.undefined())
319
- }
320
536
 
321
- /// Initiate a single activity task poll request.
322
- /// There should be only one concurrent poll request for this type.
323
- pub fn worker_poll_activity_task(mut cx: FunctionContext) -> JsResult<JsUndefined> {
324
- let worker = cx.argument::<BoxedWorker>(0)?;
325
- let callback = cx.argument::<JsFunction>(1)?;
326
- match worker.borrow().as_ref() {
327
- None => {
328
- callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Worker")?;
537
+ impl From<WorkerDeploymentOptions> for CoreWorkerDeploymentOptions {
538
+ fn from(val: WorkerDeploymentOptions) -> Self {
539
+ Self {
540
+ version: val.version.into(),
541
+ use_worker_versioning: val.use_worker_versioning,
542
+ default_versioning_behavior: Some(val.default_versioning_behavior.into()),
543
+ }
329
544
  }
330
- Some(worker) => {
331
- let request = WorkerRequest::PollActivityTask {
332
- callback: callback.root(&mut cx),
333
- };
334
- if let Err(err) = worker.sender.send(request) {
335
- callback_with_unexpected_error(&mut cx, callback, err)?;
545
+ }
546
+
547
+ impl From<WorkerDeploymentVersion> for CoreWorkerDeploymentVersion {
548
+ fn from(val: WorkerDeploymentVersion) -> Self {
549
+ Self {
550
+ build_id: val.build_id,
551
+ deployment_name: val.deployment_name,
336
552
  }
337
553
  }
338
554
  }
339
- Ok(cx.undefined())
340
- }
341
555
 
342
- /// Submit a workflow activation completion to core.
343
- pub fn worker_complete_workflow_activation(mut cx: FunctionContext) -> JsResult<JsUndefined> {
344
- let worker = cx.argument::<BoxedWorker>(0)?;
345
- let completion = cx.argument::<JsArrayBuffer>(1)?;
346
- let callback = cx.argument::<JsFunction>(2)?;
347
- match worker.borrow().as_ref() {
348
- None => {
349
- callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Worker")?;
556
+ impl From<CoreWorkerDeploymentVersion> for WorkerDeploymentVersion {
557
+ fn from(val: CoreWorkerDeploymentVersion) -> Self {
558
+ Self {
559
+ build_id: val.build_id,
560
+ deployment_name: val.deployment_name,
561
+ }
350
562
  }
351
- Some(worker) => {
352
- match WorkflowActivationCompletion::decode_length_delimited(completion.as_slice(&cx)) {
353
- Ok(completion) => {
354
- let request = WorkerRequest::CompleteWorkflowActivation {
355
- completion,
356
- callback: callback.root(&mut cx),
357
- };
358
- if let Err(err) = worker.sender.send(request) {
359
- callback_with_unexpected_error(&mut cx, callback, err)?;
360
- };
361
- }
362
- Err(_) => callback_with_error(&mut cx, callback, |cx| {
363
- JsError::type_error(cx, "Cannot decode Completion from buffer")
364
- })?,
563
+ }
564
+
565
+ impl TryIntoJs for WorkerDeploymentVersion {
566
+ type Output = JsObject;
567
+
568
+ fn try_into_js<'cx>(self, cx: &mut impl Context<'cx>) -> JsResult<'cx, Self::Output> {
569
+ let obj = cx.empty_object();
570
+ let bid = self.build_id.try_into_js(cx)?;
571
+ obj.set(cx, "buildId", bid)?;
572
+ let dn = self.deployment_name.try_into_js(cx)?;
573
+ obj.set(cx, "deploymentName", dn)?;
574
+ Ok(obj)
575
+ }
576
+ }
577
+
578
+ impl From<VersioningBehavior> for CoreVersioningBehavior {
579
+ fn from(val: VersioningBehavior) -> Self {
580
+ match val {
581
+ VersioningBehavior::Pinned => Self::Pinned,
582
+ VersioningBehavior::AutoUpgrade => Self::AutoUpgrade,
365
583
  }
366
584
  }
367
- };
368
- Ok(cx.undefined())
369
- }
585
+ }
370
586
 
371
- /// Submit an activity task completion to core.
372
- pub fn worker_complete_activity_task(mut cx: FunctionContext) -> JsResult<JsUndefined> {
373
- let worker = cx.argument::<BoxedWorker>(0)?;
374
- let result = cx.argument::<JsArrayBuffer>(1)?;
375
- let callback = cx.argument::<JsFunction>(2)?;
376
- match worker.borrow().as_ref() {
377
- None => {
378
- callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Worker")?;
587
+ #[derive(TryFromJs)]
588
+ #[allow(clippy::struct_field_names)]
589
+ pub(super) struct WorkerTuner {
590
+ workflow_task_slot_supplier: SlotSupplier<WorkflowSlotKind>,
591
+ activity_task_slot_supplier: SlotSupplier<ActivitySlotKind>,
592
+ local_activity_task_slot_supplier: SlotSupplier<LocalActivitySlotKind>,
593
+ }
594
+
595
+ impl WorkerTuner {
596
+ fn into_core_config(self) -> Result<Arc<TunerHolder>, String> {
597
+ let mut tuner_holder = TunerHolderOptionsBuilder::default();
598
+ let mut rbo = None;
599
+
600
+ tuner_holder.workflow_slot_options(
601
+ self.workflow_task_slot_supplier
602
+ .into_slot_supplier(&mut rbo),
603
+ );
604
+ tuner_holder.activity_slot_options(
605
+ self.activity_task_slot_supplier
606
+ .into_slot_supplier(&mut rbo),
607
+ );
608
+ tuner_holder.local_activity_slot_options(
609
+ self.local_activity_task_slot_supplier
610
+ .into_slot_supplier(&mut rbo),
611
+ );
612
+ if let Some(rbo) = rbo {
613
+ tuner_holder.resource_based_options(rbo);
614
+ }
615
+
616
+ tuner_holder
617
+ .build_tuner_holder()
618
+ .map(Arc::new)
619
+ .map_err(|e| format!("Invalid tuner options: {e:?}"))
379
620
  }
380
- Some(worker) => {
381
- match ActivityTaskCompletion::decode_length_delimited(result.as_slice(&cx)) {
382
- Ok(completion) => {
383
- let request = WorkerRequest::CompleteActivityTask {
384
- completion,
385
- callback: callback.root(&mut cx),
386
- };
387
- if let Err(err) = worker.sender.send(request) {
388
- callback_with_unexpected_error(&mut cx, callback, err)?;
389
- };
621
+ }
622
+
623
+ #[derive(TryFromJs)]
624
+ pub(super) enum SlotSupplier<SK: SlotKind + Send + Sync + 'static> {
625
+ FixedSize(FixedSizeSlotSupplierOptions),
626
+ ResourceBased(ResourceBasedSlotSupplierOptions),
627
+ Custom(CustomSlotSupplierOptions<SK>),
628
+ }
629
+
630
+ #[derive(TryFromJs)]
631
+ pub(super) struct FixedSizeSlotSupplierOptions {
632
+ num_slots: usize,
633
+ }
634
+
635
+ #[derive(TryFromJs)]
636
+ pub(super) struct ResourceBasedSlotSupplierOptions {
637
+ minimum_slots: usize,
638
+ maximum_slots: usize,
639
+ ramp_throttle: Duration,
640
+ tuner_options: ResourceBasedTunerOptions,
641
+ }
642
+
643
+ #[derive(TryFromJs)]
644
+ pub(super) struct ResourceBasedTunerOptions {
645
+ target_memory_usage: f64,
646
+ target_cpu_usage: f64,
647
+ }
648
+
649
+ impl<SK: SlotKind + Send + Sync + 'static> SlotSupplier<SK> {
650
+ fn into_slot_supplier(
651
+ self,
652
+ rbo: &mut Option<ResourceBasedSlotsOptions>,
653
+ ) -> CoreSlotSupplierOptions<SK> {
654
+ match self {
655
+ Self::FixedSize(opts) => CoreSlotSupplierOptions::FixedSize {
656
+ slots: opts.num_slots,
657
+ },
658
+ Self::ResourceBased(opts) => {
659
+ *rbo = Some(
660
+ ResourceBasedSlotsOptionsBuilder::default()
661
+ .target_cpu_usage(opts.tuner_options.target_cpu_usage)
662
+ .target_mem_usage(opts.tuner_options.target_memory_usage)
663
+ .build()
664
+ .expect("Building ResourceBasedSlotsOptions can't fail"),
665
+ );
666
+ CoreSlotSupplierOptions::ResourceBased(ResourceSlotOptions::new(
667
+ opts.minimum_slots,
668
+ opts.maximum_slots,
669
+ opts.ramp_throttle,
670
+ ))
390
671
  }
391
- Err(_) => callback_with_error(&mut cx, callback, |cx| {
392
- JsError::type_error(cx, "Cannot decode Completion from buffer")
393
- })?,
672
+ Self::Custom(opts) => CoreSlotSupplierOptions::Custom(Arc::new(
673
+ super::custom_slot_supplier::SlotSupplierBridge::new(opts),
674
+ )),
394
675
  }
395
676
  }
396
- };
397
- Ok(cx.undefined())
677
+ }
398
678
  }
399
679
 
400
- /// Submit an activity heartbeat to core.
401
- pub fn worker_record_activity_heartbeat(mut cx: FunctionContext) -> JsResult<JsUndefined> {
402
- let worker = cx.argument::<BoxedWorker>(0)?;
403
- let heartbeat = cx.argument::<JsArrayBuffer>(1)?;
404
- match worker.borrow().as_ref() {
405
- None => {
406
- make_named_error_from_string(&mut cx, UNEXPECTED_ERROR, "Tried to use closed Worker")
407
- .and_then(|err| cx.throw(err))?
680
+ ////////////////////////////////////////////////////////////////////////////////////////////////////
681
+
682
+ mod custom_slot_supplier {
683
+ use std::{marker::PhantomData, sync::Arc};
684
+
685
+ use neon::{context::Context, handle::Handle, prelude::*};
686
+
687
+ use temporal_sdk_core::{
688
+ SlotSupplierOptions as CoreSlotSupplierOptions,
689
+ api::worker::{
690
+ SlotInfo as CoreSlotInfo, SlotInfoTrait as _, SlotKind,
691
+ SlotKindType as CoreSlotKindType, SlotMarkUsedContext as CoreSlotMarkUsedContext,
692
+ SlotReleaseContext as CoreSlotReleaseContext,
693
+ SlotReservationContext as CoreSlotReservationContext, SlotSupplier as CoreSlotSupplier,
694
+ SlotSupplierPermit as CoreSlotSupplierPermit,
695
+ },
696
+ };
697
+
698
+ use bridge_macros::{TryFromJs, TryIntoJs};
699
+ use tracing::warn;
700
+
701
+ use crate::helpers::*;
702
+ use crate::worker::config::WorkerDeploymentVersion;
703
+ // Custom Slot Supplier ////////////////////////////////////////////////////////////////////////////
704
+
705
+ pub(super) struct SlotSupplierBridge<SK: SlotKind + Send + Sync + 'static> {
706
+ options: CustomSlotSupplierOptions<SK>,
707
+ }
708
+
709
+ impl<SK: SlotKind + Send + Sync + 'static> SlotSupplierBridge<SK> {
710
+ pub(crate) const fn new(options: CustomSlotSupplierOptions<SK>) -> Self {
711
+ Self { options }
408
712
  }
409
- Some(worker) => match ActivityHeartbeat::decode_length_delimited(heartbeat.as_slice(&cx)) {
410
- Ok(heartbeat) => {
411
- let request = WorkerRequest::RecordActivityHeartbeat { heartbeat };
412
- if let Err(err) = worker.sender.send(request) {
413
- make_named_error_from_error(&mut cx, UNEXPECTED_ERROR, err)
414
- .and_then(|err| cx.throw(err))?;
713
+ }
714
+
715
+ #[async_trait::async_trait]
716
+ impl<SK: SlotKind + Send + Sync + 'static> CoreSlotSupplier for SlotSupplierBridge<SK> {
717
+ type SlotKind = SK;
718
+
719
+ async fn reserve_slot(
720
+ &self,
721
+ ctx: &dyn CoreSlotReservationContext,
722
+ ) -> CoreSlotSupplierPermit {
723
+ loop {
724
+ let reserve_ctx = SlotReserveContext {
725
+ slot_type: SK::kind().into(),
726
+ task_queue: ctx.task_queue().to_string(),
727
+ worker_identity: ctx.worker_identity().to_string(),
728
+ worker_deployment_version: ctx
729
+ .worker_deployment_version()
730
+ .clone()
731
+ .map(Into::into),
732
+ is_sticky: ctx.is_sticky(),
733
+ };
734
+
735
+ let abort_controller = AbortController::new("Request Cancelled".to_string());
736
+
737
+ let permit_result = self
738
+ .options
739
+ .reserve_slot
740
+ .call((reserve_ctx, abort_controller.get_signal()))
741
+ .await;
742
+
743
+ // The call has already returned; it's no longer pertinent to trigger abort on drop
744
+ abort_controller.disarm();
745
+
746
+ match permit_result {
747
+ Ok(permit) => {
748
+ return CoreSlotSupplierPermit::with_user_data(BridgePermitData {
749
+ permit: Arc::new(permit),
750
+ });
751
+ }
752
+ Err(err) => {
753
+ warn!("Error reserving slot: {err:?}");
754
+ tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
755
+ continue;
756
+ }
415
757
  }
416
758
  }
417
- Err(_) => cx.throw_type_error("Cannot decode ActivityHeartbeat from buffer")?,
759
+ }
760
+
761
+ fn try_reserve_slot(
762
+ &self,
763
+ ctx: &dyn CoreSlotReservationContext,
764
+ ) -> Option<CoreSlotSupplierPermit> {
765
+ let tokio_runtime = tokio::runtime::Handle::current();
766
+ let _entered = tokio_runtime.enter();
767
+
768
+ let reserve_ctx = SlotReserveContext {
769
+ slot_type: SK::kind().into(),
770
+ task_queue: ctx.task_queue().to_string(),
771
+ worker_identity: ctx.worker_identity().to_string(),
772
+ worker_deployment_version: ctx.worker_deployment_version().clone().map(Into::into),
773
+ is_sticky: ctx.is_sticky(),
774
+ };
775
+
776
+ // Try to reserve slot synchronously
777
+ let result = self.options.try_reserve_slot.call_and_block((reserve_ctx,));
778
+
779
+ match result {
780
+ Ok(res) => res.map(|permit| {
781
+ CoreSlotSupplierPermit::with_user_data(BridgePermitData {
782
+ permit: Arc::new(permit),
783
+ })
784
+ }),
785
+ Err(err) => {
786
+ warn!("Error reserving {} slot: {:?}", SK::kind(), err);
787
+ None
788
+ }
789
+ }
790
+ }
791
+
792
+ fn mark_slot_used(&self, ctx: &dyn CoreSlotMarkUsedContext<SlotKind = Self::SlotKind>) {
793
+ let tokio_runtime = tokio::runtime::Handle::current();
794
+ let _entered = tokio_runtime.enter();
795
+
796
+ let permit_data = ctx
797
+ .permit()
798
+ .user_data::<BridgePermitData>()
799
+ .expect("Expected BridgePermitData in mark_slot_used");
800
+
801
+ let slot_info = SlotInfo::from(&ctx.info().downcast());
802
+
803
+ // Fire and forget call to mark_slot_used
804
+ let _ = self
805
+ .options
806
+ .mark_slot_used
807
+ .call_on_js_thread((SlotMarkUsedContext::<SK> {
808
+ slot_info,
809
+ permit: permit_data.permit.clone(),
810
+ _marker: PhantomData,
811
+ },));
812
+ }
813
+
814
+ fn release_slot(&self, ctx: &dyn CoreSlotReleaseContext<SlotKind = Self::SlotKind>) {
815
+ let tokio_runtime = tokio::runtime::Handle::current();
816
+ let _entered = tokio_runtime.enter();
817
+
818
+ let permit_data = ctx
819
+ .permit()
820
+ .user_data::<BridgePermitData>()
821
+ .expect("Expected BridgePermitData in release_slot");
822
+
823
+ let slot_info = ctx.info().map(|info| SlotInfo::from(&info.downcast()));
824
+
825
+ // Fire and forget call to release_slot
826
+ let _ = self
827
+ .options
828
+ .release_slot
829
+ .call_on_js_thread((SlotReleaseContext::<SK> {
830
+ slot_info,
831
+ permit: permit_data.permit.clone(),
832
+ _marker: PhantomData,
833
+ },));
834
+ }
835
+ }
836
+
837
+ #[derive(TryFromJs)]
838
+ pub(super) struct CustomSlotSupplierOptions<SK: SlotKind + Send + Sync + 'static> {
839
+ reserve_slot: JsAsyncCallback<(SlotReserveContext, AbortSignal), SlotPermitOpaqueData>,
840
+ try_reserve_slot: JsCallback<(SlotReserveContext,), Option<SlotPermitOpaqueData>>,
841
+ mark_slot_used: JsCallback<(SlotMarkUsedContext<SK>,), ()>,
842
+ release_slot: JsCallback<(SlotReleaseContext<SK>,), ()>,
843
+ }
844
+
845
+ impl<SK: SlotKind + Send + Sync + 'static> TryInto<CoreSlotSupplierOptions<SK>>
846
+ for CustomSlotSupplierOptions<SK>
847
+ {
848
+ type Error = BridgeError;
849
+
850
+ fn try_into(self) -> Result<CoreSlotSupplierOptions<SK>, Self::Error> {
851
+ Ok(CoreSlotSupplierOptions::Custom(Arc::new(
852
+ SlotSupplierBridge { options: self },
853
+ )))
854
+ }
855
+ }
856
+
857
+ #[derive(TryIntoJs)]
858
+ enum SlotInfo {
859
+ Workflow {
860
+ workflow_type: String,
861
+ is_sticky: bool,
418
862
  },
419
- };
420
- Ok(cx.undefined())
421
- }
863
+ Activity {
864
+ activity_type: String,
865
+ },
866
+ LocalActivity {
867
+ activity_type: String,
868
+ },
869
+ Nexus {
870
+ service: String,
871
+ operation: String,
872
+ },
873
+ }
422
874
 
423
- /// Request shutdown of the worker.
424
- /// Once complete Core will stop polling on new tasks and activations on worker's task queue.
425
- /// Caller should drain any pending tasks and activations and call worker_finalize_shutdown before breaking from
426
- /// the loop to ensure graceful shutdown.
427
- pub fn worker_initiate_shutdown(mut cx: FunctionContext) -> JsResult<JsUndefined> {
428
- let worker = cx.argument::<BoxedWorker>(0)?;
429
- let callback = cx.argument::<JsFunction>(1)?;
430
- match worker.borrow().as_ref() {
431
- None => {
432
- callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Worker")?;
875
+ impl<'a> From<&'a CoreSlotInfo<'a>> for SlotInfo {
876
+ fn from(info: &'a CoreSlotInfo<'a>) -> Self {
877
+ match info {
878
+ CoreSlotInfo::Workflow(info) => Self::Workflow {
879
+ workflow_type: info.workflow_type.to_string(),
880
+ is_sticky: info.is_sticky,
881
+ },
882
+ CoreSlotInfo::Activity(info) => Self::Activity {
883
+ activity_type: info.activity_type.to_string(),
884
+ },
885
+ CoreSlotInfo::LocalActivity(info) => Self::LocalActivity {
886
+ activity_type: info.activity_type.to_string(),
887
+ },
888
+ CoreSlotInfo::Nexus(info) => Self::Nexus {
889
+ service: info.service.to_string(),
890
+ operation: info.operation.to_string(),
891
+ },
892
+ }
433
893
  }
434
- Some(worker) => {
435
- if let Err(err) = worker.sender.send(WorkerRequest::InitiateShutdown {
436
- callback: callback.root(&mut cx),
437
- }) {
438
- make_named_error_from_error(&mut cx, UNEXPECTED_ERROR, err)
439
- .and_then(|err| cx.throw(err))?;
894
+ }
895
+
896
+ #[derive(TryIntoJs)]
897
+ struct SlotReserveContext {
898
+ slot_type: SlotKindType,
899
+ task_queue: String,
900
+ worker_identity: String,
901
+ worker_deployment_version: Option<WorkerDeploymentVersion>,
902
+ is_sticky: bool,
903
+ }
904
+
905
+ #[derive(TryIntoJs)]
906
+ struct SlotMarkUsedContext<SK: SlotKind> {
907
+ slot_info: SlotInfo,
908
+ permit: Arc<SlotPermitOpaqueData>,
909
+ _marker: PhantomData<SK>,
910
+ }
911
+
912
+ #[derive(TryIntoJs)]
913
+ struct SlotReleaseContext<SK: SlotKind> {
914
+ slot_info: Option<SlotInfo>,
915
+ permit: Arc<SlotPermitOpaqueData>,
916
+ _marker: PhantomData<SK>,
917
+ }
918
+
919
+ enum SlotKindType {
920
+ Workflow,
921
+ Activity,
922
+ LocalActivity,
923
+ Nexus,
924
+ }
925
+
926
+ impl TryIntoJs for SlotKindType {
927
+ type Output = JsString;
928
+ fn try_into_js<'cx>(self, cx: &mut impl Context<'cx>) -> JsResult<'cx, JsString> {
929
+ let s = match self {
930
+ Self::Workflow => "workflow",
931
+ Self::Activity => "activity",
932
+ Self::LocalActivity => "local-activity",
933
+ Self::Nexus => "nexus",
440
934
  };
935
+ Ok(cx.string(s))
936
+ }
937
+ }
938
+
939
+ impl From<CoreSlotKindType> for SlotKindType {
940
+ fn from(val: CoreSlotKindType) -> Self {
941
+ match val {
942
+ CoreSlotKindType::Workflow => Self::Workflow,
943
+ CoreSlotKindType::Activity => Self::Activity,
944
+ CoreSlotKindType::LocalActivity => Self::LocalActivity,
945
+ CoreSlotKindType::Nexus => Self::Nexus,
946
+ }
441
947
  }
442
948
  }
443
- Ok(cx.undefined())
444
- }
445
949
 
446
- pub fn worker_finalize_shutdown(mut cx: FunctionContext) -> JsResult<JsUndefined> {
447
- let worker = cx.argument::<BoxedWorker>(0)?;
448
- if worker.replace(None).is_none() {
449
- make_named_error_from_string(&mut cx, ILLEGAL_STATE_ERROR, "Worker already closed")
450
- .and_then(|err| cx.throw(err))?;
950
+ /// `BridgePermitData` holds the data associated with a slot permit.
951
+ struct BridgePermitData {
952
+ permit: Arc<SlotPermitOpaqueData>,
451
953
  }
452
954
 
453
- Ok(cx.undefined())
955
+ /// An opaque handle to a root'd JS object.
956
+ ///
957
+ /// Note that even though the public API allows `permit` to be any JS value, including
958
+ /// `undefined` or `null`, we may in fact only root JS _objects_ (including arrays and
959
+ /// functions, but not primitives). For that reason, we wrap the user's JS value in a
960
+ /// `JSObject`, and root that object instead.
961
+ struct SlotPermitOpaqueData(Root<JsObject>);
962
+
963
+ static PERMIT_DATA_FIELD: &str = "permit_data";
964
+
965
+ impl TryFromJs for SlotPermitOpaqueData {
966
+ fn try_from_js<'cx, 'b>(
967
+ cx: &mut impl Context<'cx>,
968
+ js_value: Handle<'b, JsValue>,
969
+ ) -> BridgeResult<Self> {
970
+ let obj = cx.empty_object();
971
+ obj.set(cx, PERMIT_DATA_FIELD, js_value)?;
972
+ Ok(Self(obj.root(cx)))
973
+ }
974
+ }
975
+
976
+ impl TryIntoJs for Arc<SlotPermitOpaqueData> {
977
+ type Output = JsValue;
978
+ fn try_into_js<'a>(self, cx: &mut impl Context<'a>) -> JsResult<'a, JsValue> {
979
+ let obj = self.as_ref().0.to_inner(cx);
980
+ obj.get_value(cx, PERMIT_DATA_FIELD)
981
+ }
982
+ }
454
983
  }