@temporalio/core-bridge 1.1.0 → 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/Cargo.lock +786 -54
  2. package/Cargo.toml +2 -2
  3. package/common.js +7 -3
  4. package/index.d.ts +110 -3
  5. package/index.js +2 -6
  6. package/package.json +3 -3
  7. package/releases/aarch64-apple-darwin/index.node +0 -0
  8. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  9. package/releases/x86_64-apple-darwin/index.node +0 -0
  10. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  11. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  12. package/scripts/build.js +4 -3
  13. package/sdk-core/.buildkite/docker/Dockerfile +2 -1
  14. package/sdk-core/ARCHITECTURE.md +2 -2
  15. package/sdk-core/README.md +12 -0
  16. package/sdk-core/bridge-ffi/Cargo.toml +2 -2
  17. package/sdk-core/client/Cargo.toml +6 -4
  18. package/sdk-core/client/src/lib.rs +338 -215
  19. package/sdk-core/client/src/raw.rs +352 -106
  20. package/sdk-core/client/src/retry.rs +159 -133
  21. package/sdk-core/client/src/workflow_handle/mod.rs +1 -1
  22. package/sdk-core/core/Cargo.toml +18 -9
  23. package/sdk-core/core/src/core_tests/activity_tasks.rs +63 -23
  24. package/sdk-core/core/src/core_tests/child_workflows.rs +125 -3
  25. package/sdk-core/core/src/core_tests/local_activities.rs +6 -6
  26. package/sdk-core/core/src/core_tests/workers.rs +3 -2
  27. package/sdk-core/core/src/core_tests/workflow_tasks.rs +70 -2
  28. package/sdk-core/core/src/ephemeral_server/mod.rs +499 -0
  29. package/sdk-core/core/src/lib.rs +60 -26
  30. package/sdk-core/core/src/pollers/poll_buffer.rs +4 -4
  31. package/sdk-core/core/src/replay/mod.rs +3 -3
  32. package/sdk-core/core/src/retry_logic.rs +10 -9
  33. package/sdk-core/core/src/telemetry/mod.rs +10 -7
  34. package/sdk-core/core/src/test_help/mod.rs +18 -8
  35. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +10 -10
  36. package/sdk-core/core/src/worker/activities/local_activities.rs +13 -13
  37. package/sdk-core/core/src/worker/activities.rs +6 -12
  38. package/sdk-core/core/src/worker/client.rs +193 -64
  39. package/sdk-core/core/src/worker/mod.rs +14 -19
  40. package/sdk-core/core/src/worker/workflow/driven_workflow.rs +3 -0
  41. package/sdk-core/core/src/worker/workflow/history_update.rs +5 -5
  42. package/sdk-core/core/src/worker/workflow/machines/child_workflow_state_machine.rs +133 -85
  43. package/sdk-core/core/src/worker/workflow/machines/mod.rs +3 -2
  44. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +160 -105
  45. package/sdk-core/core/src/worker/workflow/managed_run.rs +2 -1
  46. package/sdk-core/core/src/worker/workflow/mod.rs +59 -58
  47. package/sdk-core/core/src/worker/workflow/run_cache.rs +5 -3
  48. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +7 -5
  49. package/sdk-core/core-api/Cargo.toml +2 -2
  50. package/sdk-core/core-api/src/errors.rs +3 -11
  51. package/sdk-core/core-api/src/worker.rs +7 -0
  52. package/sdk-core/protos/api_upstream/.buildkite/Dockerfile +1 -1
  53. package/sdk-core/protos/api_upstream/.github/CODEOWNERS +1 -1
  54. package/sdk-core/protos/api_upstream/.github/PULL_REQUEST_TEMPLATE.md +2 -6
  55. package/sdk-core/protos/api_upstream/.github/workflows/trigger-api-go-update.yml +29 -0
  56. package/sdk-core/protos/api_upstream/Makefile +2 -2
  57. package/sdk-core/protos/api_upstream/buf.yaml +1 -0
  58. package/sdk-core/protos/api_upstream/temporal/api/batch/v1/message.proto +86 -0
  59. package/sdk-core/protos/api_upstream/temporal/api/command/v1/message.proto +26 -0
  60. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/batch_operation.proto +46 -0
  61. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/command_type.proto +7 -0
  62. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/event_type.proto +14 -0
  63. package/sdk-core/protos/api_upstream/temporal/api/enums/v1/update.proto +51 -0
  64. package/sdk-core/protos/api_upstream/temporal/api/failure/v1/message.proto +18 -0
  65. package/sdk-core/protos/api_upstream/temporal/api/history/v1/message.proto +57 -1
  66. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/request_response.proto +1 -3
  67. package/sdk-core/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +4 -2
  68. package/sdk-core/protos/api_upstream/temporal/api/replication/v1/message.proto +11 -0
  69. package/sdk-core/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +23 -0
  70. package/sdk-core/protos/api_upstream/temporal/api/update/v1/message.proto +46 -0
  71. package/sdk-core/protos/api_upstream/temporal/api/workflow/v1/message.proto +1 -0
  72. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +172 -0
  73. package/sdk-core/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +30 -0
  74. package/sdk-core/protos/grpc/health/v1/health.proto +63 -0
  75. package/sdk-core/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +18 -15
  76. package/sdk-core/protos/testsrv_upstream/Makefile +80 -0
  77. package/sdk-core/protos/testsrv_upstream/api-linter.yaml +38 -0
  78. package/sdk-core/protos/testsrv_upstream/buf.yaml +13 -0
  79. package/sdk-core/protos/testsrv_upstream/dependencies/gogoproto/gogo.proto +141 -0
  80. package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/request_response.proto +63 -0
  81. package/sdk-core/protos/testsrv_upstream/temporal/api/testservice/v1/service.proto +90 -0
  82. package/sdk-core/sdk/Cargo.toml +2 -2
  83. package/sdk-core/sdk/src/lib.rs +2 -2
  84. package/sdk-core/sdk/src/workflow_context/options.rs +36 -8
  85. package/sdk-core/sdk/src/workflow_context.rs +30 -6
  86. package/sdk-core/sdk/src/workflow_future.rs +4 -4
  87. package/sdk-core/sdk-core-protos/Cargo.toml +5 -5
  88. package/sdk-core/sdk-core-protos/build.rs +9 -1
  89. package/sdk-core/sdk-core-protos/src/history_builder.rs +6 -1
  90. package/sdk-core/sdk-core-protos/src/lib.rs +93 -32
  91. package/sdk-core/test-utils/Cargo.toml +3 -3
  92. package/sdk-core/test-utils/src/canned_histories.rs +58 -0
  93. package/sdk-core/test-utils/src/lib.rs +14 -10
  94. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +141 -0
  95. package/sdk-core/tests/integ_tests/heartbeat_tests.rs +55 -5
  96. package/sdk-core/tests/integ_tests/polling_tests.rs +1 -1
  97. package/sdk-core/tests/integ_tests/queries_tests.rs +4 -4
  98. package/sdk-core/tests/integ_tests/visibility_tests.rs +93 -0
  99. package/sdk-core/tests/integ_tests/workflow_tests/activities.rs +93 -10
  100. package/sdk-core/tests/integ_tests/workflow_tests/cancel_wf.rs +1 -1
  101. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +14 -14
  102. package/sdk-core/tests/integ_tests/workflow_tests/replay.rs +1 -1
  103. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +12 -12
  104. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +12 -1
  105. package/sdk-core/tests/integ_tests/workflow_tests/timers.rs +3 -3
  106. package/sdk-core/tests/integ_tests/workflow_tests.rs +19 -4
  107. package/sdk-core/tests/load_tests.rs +2 -1
  108. package/sdk-core/tests/main.rs +10 -0
  109. package/src/conversions.rs +138 -91
  110. package/src/helpers.rs +190 -0
  111. package/src/lib.rs +10 -912
  112. package/src/runtime.rs +436 -0
  113. package/src/testing.rs +67 -0
  114. package/src/worker.rs +465 -0
@@ -1,9 +1,11 @@
1
+ use std::collections::HashMap;
2
+
1
3
  use futures::StreamExt;
2
4
  use temporal_client::{WorkflowClientTrait, WorkflowExecutionInfo, WorkflowOptions};
3
5
  use temporal_sdk::{
4
6
  ChildWorkflowOptions, Signal, SignalWorkflowOptions, WfContext, WorkflowResult,
5
7
  };
6
- use temporal_sdk_core_protos::coresdk::IntoPayloadsExt;
8
+ use temporal_sdk_core_protos::{coresdk::IntoPayloadsExt, temporal::api::common::v1::Payload};
7
9
  use temporal_sdk_core_test_utils::CoreWfStarter;
8
10
  use uuid::Uuid;
9
11
 
@@ -57,7 +59,12 @@ async fn signal_receiver(ctx: WfContext) -> WorkflowResult<()> {
57
59
 
58
60
  async fn signal_with_create_wf_receiver(ctx: WfContext) -> WorkflowResult<()> {
59
61
  let res = ctx.make_signal_channel(SIGNAME).next().await.unwrap();
62
+ println!("HEADER: {:?}", res.headers);
60
63
  assert_eq!(&res.input, &[b"tada".into()]);
64
+ assert_eq!(
65
+ *res.headers.get("tupac").expect("tupac header exists"),
66
+ b"shakur".into()
67
+ );
61
68
  Ok(().into())
62
69
  }
63
70
 
@@ -96,15 +103,19 @@ async fn sends_signal_with_create_wf() {
96
103
  worker.register_wf("receiversignal", signal_with_create_wf_receiver);
97
104
 
98
105
  let client = starter.get_client().await;
106
+ let mut header: HashMap<String, Payload> = HashMap::new();
107
+ header.insert("tupac".into(), "shakur".into());
99
108
  let res = client
100
109
  .signal_with_start_workflow_execution(
101
110
  None,
102
111
  worker.inner_mut().task_queue().to_owned(),
103
112
  "sends_signal_with_create_wf".to_owned(),
104
113
  "receiversignal".to_owned(),
114
+ None,
105
115
  WorkflowOptions::default(),
106
116
  SIGNAME.to_owned(),
107
117
  vec![b"tada".into()].into_payloads(),
118
+ Some(header.into()),
108
119
  )
109
120
  .await
110
121
  .expect("request succeeds.qed");
@@ -42,7 +42,7 @@ async fn timer_workflow_manual() {
42
42
  task.run_id,
43
43
  vec![StartTimer {
44
44
  seq: 0,
45
- start_to_fire_timeout: Some(Duration::from_secs(1).into()),
45
+ start_to_fire_timeout: Some(prost_dur!(from_secs(1))),
46
46
  }
47
47
  .into()],
48
48
  ))
@@ -63,12 +63,12 @@ async fn timer_cancel_workflow() {
63
63
  vec![
64
64
  StartTimer {
65
65
  seq: 0,
66
- start_to_fire_timeout: Some(Duration::from_millis(50).into()),
66
+ start_to_fire_timeout: Some(prost_dur!(from_millis(50))),
67
67
  }
68
68
  .into(),
69
69
  StartTimer {
70
70
  seq: 1,
71
- start_to_fire_timeout: Some(Duration::from_secs(10).into()),
71
+ start_to_fire_timeout: Some(prost_dur!(from_secs(10))),
72
72
  }
73
73
  .into(),
74
74
  ],
@@ -223,7 +223,7 @@ async fn fail_wf_task(#[values(true, false)] replay: bool) {
223
223
  task.run_id,
224
224
  vec![StartTimer {
225
225
  seq: 0,
226
- start_to_fire_timeout: Some(Duration::from_millis(200).into()),
226
+ start_to_fire_timeout: Some(prost_dur!(from_millis(200))),
227
227
  }
228
228
  .into()],
229
229
  ))
@@ -279,6 +279,7 @@ async fn signal_workflow() {
279
279
  res.run_id.to_string(),
280
280
  signal_id_1.to_string(),
281
281
  None,
282
+ None,
282
283
  )
283
284
  .await
284
285
  .unwrap();
@@ -288,6 +289,7 @@ async fn signal_workflow() {
288
289
  res.run_id.to_string(),
289
290
  signal_id_2.to_string(),
290
291
  None,
292
+ None,
291
293
  )
292
294
  .await
293
295
  .unwrap();
@@ -345,7 +347,7 @@ async fn signal_workflow_signal_not_handled_on_workflow_completion() {
345
347
  res.run_id,
346
348
  vec![StartTimer {
347
349
  seq: 0,
348
- start_to_fire_timeout: Some(Duration::from_millis(10).into()),
350
+ start_to_fire_timeout: Some(prost_dur!(from_millis(10))),
349
351
  }
350
352
  .into()],
351
353
  ))
@@ -374,6 +376,7 @@ async fn signal_workflow_signal_not_handled_on_workflow_completion() {
374
376
  res.run_id.to_string(),
375
377
  signal_id_1.to_string(),
376
378
  None,
379
+ None,
377
380
  )
378
381
  .await
379
382
  .unwrap();
@@ -458,7 +461,13 @@ async fn wft_timeout_doesnt_create_unsolvable_autocomplete() {
458
461
  // Send the signals to the server & resolve activity -- sometimes this happens too fast
459
462
  sleep(Duration::from_millis(200)).await;
460
463
  client
461
- .signal_workflow_execution(wf_id.to_string(), rid, signal_at_start.to_string(), None)
464
+ .signal_workflow_execution(
465
+ wf_id.to_string(),
466
+ rid,
467
+ signal_at_start.to_string(),
468
+ None,
469
+ None,
470
+ )
462
471
  .await
463
472
  .unwrap();
464
473
  // Complete activity successfully.
@@ -470,7 +479,13 @@ async fn wft_timeout_doesnt_create_unsolvable_autocomplete() {
470
479
  .unwrap();
471
480
  let rid = wf_task.run_id.clone();
472
481
  client
473
- .signal_workflow_execution(wf_id.to_string(), rid, signal_at_complete.to_string(), None)
482
+ .signal_workflow_execution(
483
+ wf_id.to_string(),
484
+ rid,
485
+ signal_at_complete.to_string(),
486
+ None,
487
+ None,
488
+ )
474
489
  .await
475
490
  .unwrap();
476
491
  // Now poll again, it will be an eviction b/c non-sticky mode.
@@ -35,13 +35,13 @@ async fn activity_load() {
35
35
  let activity = ActivityOptions {
36
36
  activity_id: Some(activity_id.to_string()),
37
37
  activity_type: "test_activity".to_string(),
38
- input: Default::default(),
39
38
  task_queue,
40
39
  schedule_to_start_timeout: Some(activity_timeout),
41
40
  start_to_close_timeout: Some(activity_timeout),
42
41
  schedule_to_close_timeout: Some(activity_timeout),
43
42
  heartbeat_timeout: Some(activity_timeout),
44
43
  cancellation_type: ActivityCancellationType::TryCancel,
44
+ ..Default::default()
45
45
  };
46
46
  let res = ctx.activity(activity).await.unwrap_ok_payload();
47
47
  assert_eq!(res.data, payload_dat);
@@ -175,6 +175,7 @@ async fn workflow_load() {
175
175
  "".to_string(),
176
176
  SIGNAME.to_string(),
177
177
  None,
178
+ None,
178
179
  )
179
180
  })
180
181
  .collect();
@@ -2,10 +2,20 @@
2
2
 
3
3
  #[cfg(test)]
4
4
  mod integ_tests {
5
+ #[macro_export]
6
+ macro_rules! prost_dur {
7
+ ($dur_call:ident $args:tt) => {
8
+ std::time::Duration::$dur_call$args
9
+ .try_into()
10
+ .expect("test duration fits")
11
+ };
12
+ }
5
13
  mod client_tests;
14
+ mod ephemeral_server_tests;
6
15
  mod heartbeat_tests;
7
16
  mod polling_tests;
8
17
  mod queries_tests;
18
+ mod visibility_tests;
9
19
  mod workflow_tests;
10
20
 
11
21
  use std::str::FromStr;
@@ -1,5 +1,5 @@
1
+ use crate::helpers::*;
1
2
  use log::LevelFilter;
2
- use neon::types::buffer::TypedArray;
3
3
  use neon::{
4
4
  context::Context,
5
5
  handle::Handle,
@@ -7,107 +7,50 @@ use neon::{
7
7
  types::{JsBoolean, JsNumber, JsString},
8
8
  };
9
9
  use opentelemetry::trace::{SpanContext, SpanId, TraceFlags, TraceId, TraceState};
10
- use std::{collections::HashMap, fmt::Display, net::SocketAddr, str::FromStr, time::Duration};
10
+ use std::{collections::HashMap, net::SocketAddr, str::FromStr, time::Duration};
11
11
  use temporal_sdk_core::{
12
12
  api::worker::{WorkerConfig, WorkerConfigBuilder},
13
+ ephemeral_server::{
14
+ TemporaliteConfig, TemporaliteConfigBuilder, TestServerConfig, TestServerConfigBuilder,
15
+ },
13
16
  ClientOptions, ClientOptionsBuilder, ClientTlsConfig, Logger, MetricsExporter,
14
17
  OtelCollectorOptions, RetryConfig, TelemetryOptions, TelemetryOptionsBuilder, TlsConfig,
15
18
  TraceExporter, Url,
16
19
  };
17
20
 
18
- #[macro_export]
19
- macro_rules! js_optional_getter {
20
- ($js_cx:expr, $js_obj:expr, $prop_name:expr, $js_type:ty) => {
21
- match get_optional($js_cx, $js_obj, $prop_name) {
22
- None => None,
23
- Some(val) => {
24
- if val.is_a::<$js_type, _>($js_cx) {
25
- Some(val.downcast_or_throw::<$js_type, _>($js_cx)?)
26
- } else {
27
- Some($js_cx.throw_type_error(format!("Invalid {}", $prop_name))?)
28
- }
29
- }
30
- }
31
- };
21
+ pub enum EphemeralServerConfig {
22
+ TestServer(TestServerConfig),
23
+ Temporalite(TemporaliteConfig),
32
24
  }
33
25
 
34
- macro_rules! js_value_getter {
35
- ($js_cx:expr, $js_obj:expr, $prop_name:expr, $js_type:ty) => {
36
- match js_optional_getter!($js_cx, $js_obj, $prop_name, $js_type) {
37
- Some(val) => val.value($js_cx),
38
- None => $js_cx.throw_type_error(format!("{} must be defined", $prop_name))?,
39
- }
40
- };
26
+ pub trait ArrayHandleConversionsExt {
27
+ fn to_vec_of_string(&self, cx: &mut FunctionContext) -> NeonResult<Vec<String>>;
41
28
  }
42
29
 
43
- /// Helper for extracting an optional attribute from [obj].
44
- /// If [obj].[attr] is undefined or not present, None is returned
45
- pub fn get_optional<'a, C, K>(
46
- cx: &mut C,
47
- obj: &Handle<JsObject>,
48
- attr: K,
49
- ) -> Option<Handle<'a, JsValue>>
50
- where
51
- K: neon::object::PropertyKey,
52
- C: Context<'a>,
53
- {
54
- match obj.get_value(cx, attr) {
55
- Err(_) => None,
56
- Ok(val) => match val.is_a::<JsUndefined, _>(cx) {
57
- true => None,
58
- false => Some(val),
59
- },
60
- }
61
- }
30
+ impl ArrayHandleConversionsExt for Handle<'_, JsArray> {
31
+ fn to_vec_of_string(&self, cx: &mut FunctionContext) -> NeonResult<Vec<String>> {
32
+ let js_vec = self.to_vec(cx)?;
33
+ let len = js_vec.len();
34
+ let mut ret_vec = Vec::<String>::with_capacity(len);
62
35
 
63
- /// Helper for extracting a Vec<u8> from optional Buffer at [obj].[attr]
64
- fn get_optional_vec<'a, C, K>(
65
- cx: &mut C,
66
- obj: &Handle<JsObject>,
67
- attr: K,
68
- ) -> Result<Option<Vec<u8>>, neon::result::Throw>
69
- where
70
- K: neon::object::PropertyKey + Display + Clone,
71
- C: Context<'a>,
72
- {
73
- if let Some(val) = get_optional(cx, obj, attr.clone()) {
74
- let buf = val.downcast::<JsBuffer, C>(cx).map_err(|_| {
75
- cx.throw_type_error::<_, Option<Vec<u8>>>(format!("Invalid {}", attr))
76
- .unwrap_err()
77
- })?;
78
- Ok(Some(buf.as_slice(cx).to_vec()))
79
- } else {
80
- Ok(None)
81
- }
82
- }
83
-
84
- /// Helper for extracting a Vec<u8> from optional Buffer at [obj].[attr]
85
- fn get_vec<'a, C, K>(
86
- cx: &mut C,
87
- obj: &Handle<JsObject>,
88
- attr: K,
89
- full_attr_path: &str,
90
- ) -> Result<Vec<u8>, neon::result::Throw>
91
- where
92
- K: neon::object::PropertyKey + Display + Clone,
93
- C: Context<'a>,
94
- {
95
- if let Some(val) = get_optional(cx, obj, attr.clone()) {
96
- let buf = val.downcast::<JsBuffer, C>(cx).map_err(|_| {
97
- cx.throw_type_error::<_, Option<Vec<u8>>>(format!("Invalid {}", attr))
98
- .unwrap_err()
99
- })?;
100
- Ok(buf.as_slice(cx).to_vec())
101
- } else {
102
- cx.throw_type_error::<_, Vec<u8>>(format!("Invalid or missing {}", full_attr_path))
36
+ for i in 0..len - 1 {
37
+ ret_vec[i] = js_vec[i].downcast_or_throw::<JsString, _>(cx)?.value(cx);
38
+ }
39
+ Ok(ret_vec)
103
40
  }
104
41
  }
105
42
 
106
- pub(crate) trait ObjectHandleConversionsExt {
43
+ pub trait ObjectHandleConversionsExt {
44
+ fn set_default(&self, cx: &mut FunctionContext, key: &str, value: &str) -> NeonResult<()>;
107
45
  fn as_otel_span_context(&self, ctx: &mut FunctionContext) -> NeonResult<SpanContext>;
108
46
  fn as_client_options(&self, ctx: &mut FunctionContext) -> NeonResult<ClientOptions>;
109
47
  fn as_telemetry_options(&self, cx: &mut FunctionContext) -> NeonResult<TelemetryOptions>;
110
48
  fn as_worker_config(&self, cx: &mut FunctionContext) -> NeonResult<WorkerConfig>;
49
+ fn as_ephemeral_server_config(
50
+ &self,
51
+ cx: &mut FunctionContext,
52
+ sdk_version: String,
53
+ ) -> NeonResult<EphemeralServerConfig>;
111
54
  fn as_hash_map_of_string_to_string(
112
55
  &self,
113
56
  cx: &mut FunctionContext,
@@ -154,8 +97,7 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
154
97
  let tls_cfg = match js_optional_getter!(cx, self, "tls", JsObject) {
155
98
  None => None,
156
99
  Some(tls) => {
157
- let domain = js_optional_getter!(cx, &tls, "serverNameOverride", JsString)
158
- .map(|h| h.value(cx));
100
+ let domain = js_optional_value_getter!(cx, &tls, "serverNameOverride", JsString);
159
101
 
160
102
  let server_root_ca_cert = get_optional_vec(cx, &tls, "serverRootCACertificate")?;
161
103
  let client_tls_config =
@@ -207,13 +149,13 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
207
149
  "maxInterval",
208
150
  JsNumber
209
151
  ) as u64),
210
- max_elapsed_time: js_optional_getter!(
152
+ max_elapsed_time: js_optional_value_getter!(
211
153
  cx,
212
154
  &retry_config,
213
155
  "maxElapsedTime",
214
156
  JsNumber
215
157
  )
216
- .map(|val| Duration::from_millis(val.value(cx) as u64)),
158
+ .map(|val| Duration::from_millis(val as u64)),
217
159
  max_retries: js_value_getter!(cx, retry_config, "maxRetries", JsNumber) as usize,
218
160
  },
219
161
  };
@@ -235,12 +177,11 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
235
177
  fn as_telemetry_options(&self, cx: &mut FunctionContext) -> NeonResult<TelemetryOptions> {
236
178
  let mut telemetry_opts = TelemetryOptionsBuilder::default();
237
179
 
238
- if let Some(tf) = js_optional_getter!(cx, self, "tracingFilter", JsString) {
239
- telemetry_opts.tracing_filter(tf.value(cx));
180
+ if let Some(tf) = js_optional_value_getter!(cx, self, "tracingFilter", JsString) {
181
+ telemetry_opts.tracing_filter(tf);
240
182
  }
241
183
  telemetry_opts.no_temporal_prefix_for_metrics(
242
- js_optional_getter!(cx, self, "noTemporalPrefixForMetrics", JsBoolean)
243
- .map(|b| b.value(cx))
184
+ js_optional_value_getter!(cx, self, "noTemporalPrefixForMetrics", JsBoolean)
244
185
  .unwrap_or_default(),
245
186
  );
246
187
  if let Some(ref logging) = js_optional_getter!(cx, self, "logging", JsObject) {
@@ -382,4 +323,110 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
382
323
  Err(e) => cx.throw_error(format!("Invalid worker config: {:?}", e)),
383
324
  }
384
325
  }
326
+
327
+ fn set_default(&self, cx: &mut FunctionContext, key: &str, value: &str) -> NeonResult<()> {
328
+ let key = cx.string(key);
329
+ let existing: Option<Handle<JsString>> = self.get_opt(cx, key)?;
330
+ if existing.is_none() {
331
+ let value = cx.string(value);
332
+ self.set(cx, key, value)?;
333
+ }
334
+ Ok(())
335
+ }
336
+
337
+ fn as_ephemeral_server_config(
338
+ &self,
339
+ cx: &mut FunctionContext,
340
+ sdk_version: String,
341
+ ) -> NeonResult<EphemeralServerConfig> {
342
+ let js_executable = js_optional_getter!(cx, self, "executable", JsObject)
343
+ .unwrap_or_else(|| cx.empty_object());
344
+ js_executable.set_default(cx, "type", "cached-download")?;
345
+
346
+ let exec_type = js_value_getter!(cx, &js_executable, "type", JsString);
347
+ let executable = match exec_type.as_str() {
348
+ "cached-download" => {
349
+ let version = js_optional_value_getter!(cx, &js_executable, "version", JsString)
350
+ .unwrap_or("default".to_owned());
351
+ let dest_dir =
352
+ js_optional_value_getter!(cx, &js_executable, "downloadDir", JsString);
353
+
354
+ let exec_version = match version.as_str() {
355
+ "default" => {
356
+ temporal_sdk_core::ephemeral_server::EphemeralExeVersion::Default {
357
+ sdk_name: "sdk-typescript".to_owned(),
358
+ sdk_version,
359
+ }
360
+ }
361
+ _ => temporal_sdk_core::ephemeral_server::EphemeralExeVersion::Fixed(version),
362
+ };
363
+ temporal_sdk_core::ephemeral_server::EphemeralExe::CachedDownload {
364
+ version: exec_version,
365
+ dest_dir,
366
+ }
367
+ }
368
+ "existing-path" => {
369
+ let path = js_value_getter!(cx, &js_executable, "path", JsString);
370
+ temporal_sdk_core::ephemeral_server::EphemeralExe::ExistingPath(path)
371
+ }
372
+ _ => {
373
+ return cx.throw_type_error(format!("Invalid executable type: {}", exec_type))?;
374
+ }
375
+ };
376
+ let port = js_optional_getter!(cx, self, "port", JsNumber).map(|s| s.value(cx) as u16);
377
+
378
+ let server_type = js_value_getter!(cx, self, "type", JsString);
379
+ match server_type.as_str() {
380
+ "temporalite" => {
381
+ let mut config = TemporaliteConfigBuilder::default();
382
+ config.exe(executable).port(port);
383
+
384
+ if let Some(extra_args) = js_optional_getter!(cx, self, "extraArgs", JsArray) {
385
+ config.extra_args(extra_args.to_vec_of_string(cx)?);
386
+ };
387
+ if let Some(namespace) = js_optional_value_getter!(cx, self, "namespace", JsString)
388
+ {
389
+ config.namespace(namespace);
390
+ }
391
+ if let Some(ip) = js_optional_value_getter!(cx, self, "ip", JsString) {
392
+ config.ip(ip);
393
+ }
394
+ config.db_filename(js_optional_value_getter!(cx, self, "dbFilename", JsString));
395
+ config.ui(js_optional_value_getter!(cx, self, "ui", JsBoolean).unwrap_or_default());
396
+
397
+ if let Some(log) = js_optional_getter!(cx, self, "log", JsObject) {
398
+ let format = js_value_getter!(cx, &log, "format", JsString);
399
+ let level = js_value_getter!(cx, &log, "level", JsString);
400
+ config.log((format, level));
401
+ }
402
+
403
+ match config.build() {
404
+ Ok(config) => Ok(EphemeralServerConfig::Temporalite(config)),
405
+ Err(err) => {
406
+ cx.throw_type_error(format!("Invalid temporalite config: {:?}", err))
407
+ }
408
+ }
409
+ }
410
+ "time-skipping" => {
411
+ let mut config = TestServerConfigBuilder::default();
412
+ config.exe(executable).port(port);
413
+
414
+ if let Some(extra_args_js) = js_optional_getter!(cx, self, "extraArgs", JsArray) {
415
+ let extra_args = extra_args_js.to_vec_of_string(cx)?;
416
+ config.extra_args(extra_args);
417
+ };
418
+
419
+ match config.build() {
420
+ Ok(config) => Ok(EphemeralServerConfig::TestServer(config)),
421
+ Err(err) => {
422
+ cx.throw_type_error(format!("Invalid test server config: {:?}", err))
423
+ }
424
+ }
425
+ }
426
+ s => cx.throw_type_error(format!(
427
+ "Invalid ephemeral server type: {}, expected 'temporalite' or 'time-skipping'",
428
+ s
429
+ )),
430
+ }
431
+ }
385
432
  }
package/src/helpers.rs ADDED
@@ -0,0 +1,190 @@
1
+ use crate::errors::*;
2
+ use neon::prelude::*;
3
+ use neon::types::buffer::TypedArray;
4
+ use std::{fmt::Display, future::Future, sync::Arc};
5
+
6
+ /// Send a result to JS via callback using a [Channel]
7
+ pub fn send_result<F, T>(channel: Arc<Channel>, callback: Root<JsFunction>, res_fn: F)
8
+ where
9
+ F: for<'a> FnOnce(&mut TaskContext<'a>) -> NeonResult<Handle<'a, T>> + Send + 'static,
10
+ T: Value,
11
+ {
12
+ channel.send(move |mut cx| {
13
+ let callback = callback.into_inner(&mut cx);
14
+ let this = cx.undefined();
15
+ let error = cx.undefined();
16
+ let result = res_fn(&mut cx)?;
17
+ let args: Vec<Handle<JsValue>> = vec![error.upcast(), result.upcast()];
18
+ callback.call(&mut cx, this, args)?;
19
+ Ok(())
20
+ });
21
+ }
22
+
23
+ /// Send an error to JS via callback using a [Channel]
24
+ pub fn send_error<E, F>(channel: Arc<Channel>, callback: Root<JsFunction>, error_ctor: F)
25
+ where
26
+ E: Object,
27
+ F: for<'a> FnOnce(&mut TaskContext<'a>) -> JsResult<'a, E> + Send + 'static,
28
+ {
29
+ channel.send(move |mut cx| {
30
+ let callback = callback.into_inner(&mut cx);
31
+ callback_with_error(&mut cx, callback, error_ctor)
32
+ });
33
+ }
34
+
35
+ /// Call `callback` with given error
36
+ pub fn callback_with_error<'a, C, E, F>(
37
+ cx: &mut C,
38
+ callback: Handle<JsFunction>,
39
+ error_ctor: F,
40
+ ) -> NeonResult<()>
41
+ where
42
+ C: Context<'a>,
43
+ E: Object,
44
+ F: FnOnce(&mut C) -> JsResult<'a, E> + Send + 'static,
45
+ {
46
+ let this = cx.undefined();
47
+ let error = error_ctor(cx)?;
48
+ let result = cx.undefined();
49
+ let args: Vec<Handle<JsValue>> = vec![error.upcast(), result.upcast()];
50
+ callback.call(cx, this, args)?;
51
+ Ok(())
52
+ }
53
+
54
+ /// Call `callback` with an UnexpectedError created from `err`
55
+ pub fn callback_with_unexpected_error<'a, C, E>(
56
+ cx: &mut C,
57
+ callback: Handle<JsFunction>,
58
+ err: E,
59
+ ) -> NeonResult<()>
60
+ where
61
+ C: Context<'a>,
62
+ E: std::fmt::Display,
63
+ {
64
+ let err_str = format!("{}", err);
65
+ callback_with_error(cx, callback, move |cx| {
66
+ UNEXPECTED_ERROR.from_string(cx, err_str)
67
+ })
68
+ }
69
+
70
+ /// When Future completes, call given JS callback using a neon::Channel with either error or
71
+ /// undefined
72
+ pub async fn void_future_to_js<E, F, ER, EF>(
73
+ channel: Arc<Channel>,
74
+ callback: Root<JsFunction>,
75
+ f: F,
76
+ error_function: EF,
77
+ ) where
78
+ E: Display + Send + 'static,
79
+ F: Future<Output = Result<(), E>> + Send,
80
+ ER: Object,
81
+ EF: for<'a> FnOnce(&mut TaskContext<'a>, E) -> JsResult<'a, ER> + Send + 'static,
82
+ {
83
+ match f.await {
84
+ Ok(()) => {
85
+ send_result(channel, callback, |cx| Ok(cx.undefined()));
86
+ }
87
+ Err(err) => {
88
+ send_error(channel, callback, |cx| error_function(cx, err));
89
+ }
90
+ }
91
+ }
92
+
93
+ macro_rules! js_optional_getter {
94
+ ($js_cx:expr, $js_obj:expr, $prop_name:expr, $js_type:ty) => {
95
+ match get_optional($js_cx, $js_obj, $prop_name) {
96
+ None => None,
97
+ Some(val) => {
98
+ if val.is_a::<$js_type, _>($js_cx) {
99
+ Some(val.downcast_or_throw::<$js_type, _>($js_cx)?)
100
+ } else {
101
+ Some($js_cx.throw_type_error(format!("Invalid {}", $prop_name))?)
102
+ }
103
+ }
104
+ }
105
+ };
106
+ }
107
+
108
+ pub(crate) use js_optional_getter;
109
+
110
+ macro_rules! js_optional_value_getter {
111
+ ($js_cx:expr, $js_obj:expr, $prop_name:expr, $js_type:ty) => {
112
+ js_optional_getter!($js_cx, $js_obj, $prop_name, $js_type).map(|v| v.value($js_cx))
113
+ };
114
+ }
115
+
116
+ pub(crate) use js_optional_value_getter;
117
+
118
+ macro_rules! js_value_getter {
119
+ ($js_cx:expr, $js_obj:expr, $prop_name:expr, $js_type:ty) => {
120
+ match js_optional_getter!($js_cx, $js_obj, $prop_name, $js_type) {
121
+ Some(val) => val.value($js_cx),
122
+ None => $js_cx.throw_type_error(format!("{} must be defined", $prop_name))?,
123
+ }
124
+ };
125
+ }
126
+
127
+ pub(crate) use js_value_getter;
128
+
129
+ /// Helper for extracting an optional attribute from [obj].
130
+ /// If [obj].[attr] is undefined or not present, None is returned
131
+ pub fn get_optional<'a, C, K>(
132
+ cx: &mut C,
133
+ obj: &Handle<JsObject>,
134
+ attr: K,
135
+ ) -> Option<Handle<'a, JsValue>>
136
+ where
137
+ K: neon::object::PropertyKey,
138
+ C: Context<'a>,
139
+ {
140
+ match obj.get_value(cx, attr) {
141
+ Err(_) => None,
142
+ Ok(val) => match val.is_a::<JsUndefined, _>(cx) {
143
+ true => None,
144
+ false => Some(val),
145
+ },
146
+ }
147
+ }
148
+
149
+ /// Helper for extracting a Vec<u8> from optional Buffer at [obj].[attr]
150
+ pub fn get_optional_vec<'a, C, K>(
151
+ cx: &mut C,
152
+ obj: &Handle<JsObject>,
153
+ attr: K,
154
+ ) -> Result<Option<Vec<u8>>, neon::result::Throw>
155
+ where
156
+ K: neon::object::PropertyKey + Display + Clone,
157
+ C: Context<'a>,
158
+ {
159
+ if let Some(val) = get_optional(cx, obj, attr.clone()) {
160
+ let buf = val.downcast::<JsBuffer, C>(cx).map_err(|_| {
161
+ cx.throw_type_error::<_, Option<Vec<u8>>>(format!("Invalid {}", attr))
162
+ .unwrap_err()
163
+ })?;
164
+ Ok(Some(buf.as_slice(cx).to_vec()))
165
+ } else {
166
+ Ok(None)
167
+ }
168
+ }
169
+
170
+ /// Helper for extracting a Vec<u8> from optional Buffer at [obj].[attr]
171
+ pub fn get_vec<'a, C, K>(
172
+ cx: &mut C,
173
+ obj: &Handle<JsObject>,
174
+ attr: K,
175
+ full_attr_path: &str,
176
+ ) -> Result<Vec<u8>, neon::result::Throw>
177
+ where
178
+ K: neon::object::PropertyKey + Display + Clone,
179
+ C: Context<'a>,
180
+ {
181
+ if let Some(val) = get_optional(cx, obj, attr.clone()) {
182
+ let buf = val.downcast::<JsBuffer, C>(cx).map_err(|_| {
183
+ cx.throw_type_error::<_, Option<Vec<u8>>>(format!("Invalid {}", attr))
184
+ .unwrap_err()
185
+ })?;
186
+ Ok(buf.as_slice(cx).to_vec())
187
+ } else {
188
+ cx.throw_type_error::<_, Vec<u8>>(format!("Invalid or missing {}", full_attr_path))
189
+ }
190
+ }