@temporalio/core-bridge 0.22.0 → 0.23.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.
@@ -6,11 +6,12 @@ use neon::{
6
6
  types::{JsNumber, JsString},
7
7
  };
8
8
  use opentelemetry::trace::{SpanContext, SpanId, TraceFlags, TraceId, TraceState};
9
- use std::{fmt::Display, net::SocketAddr, str::FromStr, time::Duration};
9
+ use std::{collections::HashMap, fmt::Display, net::SocketAddr, str::FromStr, time::Duration};
10
10
  use temporal_sdk_core::{
11
11
  api::worker::{WorkerConfig, WorkerConfigBuilder},
12
- ClientOptions, ClientOptionsBuilder, ClientTlsConfig, RetryConfig, TelemetryOptions,
13
- TelemetryOptionsBuilder, TlsConfig, Url,
12
+ ClientOptions, ClientOptionsBuilder, ClientTlsConfig, Logger, MetricsExporter,
13
+ OtelCollectorOptions, RetryConfig, TelemetryOptions, TelemetryOptionsBuilder, TlsConfig,
14
+ TraceExporter, Url,
14
15
  };
15
16
 
16
17
  macro_rules! js_value_getter {
@@ -27,6 +28,7 @@ macro_rules! js_value_getter {
27
28
  };
28
29
  }
29
30
 
31
+ #[macro_export]
30
32
  macro_rules! js_optional_getter {
31
33
  ($js_cx:expr, $js_obj:expr, $prop_name:expr, $js_type:ty) => {
32
34
  match get_optional($js_cx, $js_obj, $prop_name) {
@@ -42,7 +44,7 @@ macro_rules! js_optional_getter {
42
44
 
43
45
  /// Helper for extracting an optional attribute from [obj].
44
46
  /// If [obj].[attr] is undefined or not present, None is returned
45
- fn get_optional<'a, C, K>(
47
+ pub fn get_optional<'a, C, K>(
46
48
  cx: &mut C,
47
49
  obj: &Handle<JsObject>,
48
50
  attr: K,
@@ -108,6 +110,10 @@ pub(crate) trait ObjectHandleConversionsExt {
108
110
  fn as_client_options(&self, ctx: &mut FunctionContext) -> NeonResult<ClientOptions>;
109
111
  fn as_telemetry_options(&self, cx: &mut FunctionContext) -> NeonResult<TelemetryOptions>;
110
112
  fn as_worker_config(&self, cx: &mut FunctionContext) -> NeonResult<WorkerConfig>;
113
+ fn as_hash_map_of_string_to_string(
114
+ &self,
115
+ cx: &mut FunctionContext,
116
+ ) -> NeonResult<HashMap<String, String>>;
111
117
  }
112
118
 
113
119
  impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
@@ -124,6 +130,22 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
124
130
  ))
125
131
  }
126
132
 
133
+ fn as_hash_map_of_string_to_string(
134
+ &self,
135
+ cx: &mut FunctionContext,
136
+ ) -> NeonResult<HashMap<String, String>> {
137
+ let props = self.get_own_property_names(cx)?;
138
+ let props = props.to_vec(cx)?;
139
+ let mut map = HashMap::new();
140
+ for k in props {
141
+ let k = k.to_string(cx)?;
142
+ let v = self.get(cx, k)?.to_string(cx)?.value(cx);
143
+ let k = k.value(cx);
144
+ map.insert(k, v);
145
+ }
146
+ Ok(map)
147
+ }
148
+
127
149
  fn as_client_options(&self, cx: &mut FunctionContext) -> NeonResult<ClientOptions> {
128
150
  let url = match Url::parse(&js_value_getter!(cx, self, "url", JsString)) {
129
151
  Ok(url) => url,
@@ -198,11 +220,12 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
198
220
  },
199
221
  };
200
222
 
201
- let mut gateway_opts = ClientOptionsBuilder::default();
223
+ let mut client_options = ClientOptionsBuilder::default();
202
224
  if let Some(tls_cfg) = tls_cfg {
203
- gateway_opts.tls_cfg(tls_cfg);
225
+ client_options.tls_cfg(tls_cfg);
204
226
  }
205
- Ok(gateway_opts
227
+
228
+ Ok(client_options
206
229
  .client_name("temporal-typescript".to_string())
207
230
  .client_version(js_value_getter!(cx, self, "sdkVersion", JsString))
208
231
  .target_url(url)
@@ -214,37 +237,85 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
214
237
  }
215
238
 
216
239
  fn as_telemetry_options(&self, cx: &mut FunctionContext) -> NeonResult<TelemetryOptions> {
217
- let log_forwarding_level_str =
218
- js_value_getter!(cx, self, "logForwardingLevel", JsString).replace("WARNING", "WARN");
219
- let log_forwarding_level =
220
- LevelFilter::from_str(&log_forwarding_level_str).unwrap_or(LevelFilter::Off);
221
240
  let mut telemetry_opts = TelemetryOptionsBuilder::default();
222
- if let Some(url) = js_optional_getter!(cx, self, "oTelCollectorUrl", JsString) {
223
- let url = match Url::parse(&url.value(cx)) {
224
- Ok(url) => url,
225
- Err(_) => cx.throw_type_error("Invalid telemetryOptions.oTelCollectorUrl")?,
226
- };
227
- telemetry_opts.otel_collector_url(url);
241
+
242
+ if let Some(tf) = js_optional_getter!(cx, self, "tracingFilter", JsString) {
243
+ telemetry_opts.tracing_filter(tf.value(cx));
228
244
  }
229
- if let Some(addr) = js_optional_getter!(cx, self, "prometheusMetricsBindAddress", JsString)
230
- {
231
- match addr.value(cx).parse::<SocketAddr>() {
232
- Ok(address) => telemetry_opts.prometheus_export_bind_address(address),
233
- Err(_) => {
234
- cx.throw_type_error("Invalid telemetryOptions.prometheusMetricsBindAddress")?
245
+ if let Some(ref logging) = js_optional_getter!(cx, self, "logging", JsObject) {
246
+ if let Some(_) = get_optional(cx, logging, "console") {
247
+ telemetry_opts.logging(Logger::Console);
248
+ } else if let Some(forward) = js_optional_getter!(cx, logging, "forward", JsObject) {
249
+ let level = js_value_getter!(cx, forward, "level", JsString);
250
+ match LevelFilter::from_str(&level) {
251
+ Ok(level) => {
252
+ telemetry_opts.logging(Logger::Forward(level));
253
+ }
254
+ Err(err) => cx.throw_type_error(format!(
255
+ "Invalid telemetryOptions.logging.forward.level: {}",
256
+ err
257
+ ))?,
235
258
  }
236
- };
259
+ } else {
260
+ cx.throw_type_error(format!(
261
+ "Invalid telemetryOptions.logging, missing `console` or `forward` option"
262
+ ))?
263
+ }
237
264
  }
238
- if let Some(tf) = js_optional_getter!(cx, self, "tracingFilter", JsString) {
239
- telemetry_opts.tracing_filter(tf.value(cx));
265
+ if let Some(metrics) = js_optional_getter!(cx, self, "metrics", JsObject) {
266
+ if let Some(ref prom) = js_optional_getter!(cx, &metrics, "prometheus", JsObject) {
267
+ let addr = js_value_getter!(cx, prom, "bindAddress", JsString);
268
+ match addr.parse::<SocketAddr>() {
269
+ Ok(address) => telemetry_opts.metrics(MetricsExporter::Prometheus(address)),
270
+ Err(_) => cx.throw_type_error(
271
+ "Invalid telemetryOptions.metrics.prometheus.bindAddress",
272
+ )?,
273
+ };
274
+ } else if let Some(ref otel) = js_optional_getter!(cx, &metrics, "otel", JsObject) {
275
+ let url = js_value_getter!(cx, otel, "url", JsString);
276
+ let url = match Url::parse(&url) {
277
+ Ok(url) => url,
278
+ Err(_) => cx.throw_type_error("Invalid telemetryOptions.metrics.otel.url")?,
279
+ };
280
+ let headers =
281
+ if let Some(headers) = js_optional_getter!(cx, otel, "headers", JsObject) {
282
+ headers.as_hash_map_of_string_to_string(cx)?
283
+ } else {
284
+ Default::default()
285
+ };
286
+ telemetry_opts
287
+ .metrics(MetricsExporter::Otel(OtelCollectorOptions { url, headers }));
288
+ } else {
289
+ cx.throw_type_error(format!(
290
+ "Invalid telemetryOptions.metrics, missing `prometheus` or `otel` option"
291
+ ))?
292
+ }
240
293
  }
241
- telemetry_opts
242
- .log_forwarding_level(log_forwarding_level)
243
- .build()
244
- .map_err(|reason| {
245
- cx.throw_type_error::<_, TelemetryOptions>(format!("{}", reason))
246
- .unwrap_err()
247
- })
294
+
295
+ if let Some(tracing) = js_optional_getter!(cx, self, "tracing", JsObject) {
296
+ if let Some(ref otel) = js_optional_getter!(cx, &tracing, "otel", JsObject) {
297
+ let url = js_value_getter!(cx, otel, "url", JsString);
298
+ let url = match Url::parse(&url) {
299
+ Ok(url) => url,
300
+ Err(_) => cx.throw_type_error("Invalid telemetryOptions.tracing.otel.url")?,
301
+ };
302
+ let headers =
303
+ if let Some(headers) = js_optional_getter!(cx, otel, "headers", JsObject) {
304
+ headers.as_hash_map_of_string_to_string(cx)?
305
+ } else {
306
+ Default::default()
307
+ };
308
+ telemetry_opts.tracing(TraceExporter::Otel(OtelCollectorOptions { url, headers }));
309
+ } else {
310
+ cx.throw_type_error(format!(
311
+ "Invalid telemetryOptions.tracing, missing `otel` option"
312
+ ))?
313
+ }
314
+ }
315
+ telemetry_opts.build().map_err(|reason| {
316
+ cx.throw_type_error::<_, TelemetryOptions>(format!("{}", reason))
317
+ .unwrap_err()
318
+ })
248
319
  }
249
320
 
250
321
  fn as_worker_config(&self, cx: &mut FunctionContext) -> NeonResult<WorkerConfig> {
package/src/lib.rs CHANGED
@@ -1,12 +1,14 @@
1
1
  mod conversions;
2
2
  mod errors;
3
3
 
4
- use crate::conversions::ObjectHandleConversionsExt;
4
+ use crate::conversions::{get_optional, ObjectHandleConversionsExt};
5
5
  use errors::*;
6
6
  use neon::prelude::*;
7
7
  use once_cell::sync::OnceCell;
8
8
  use opentelemetry::trace::{FutureExt, SpanContext, TraceContextExt};
9
+ use parking_lot::RwLock;
9
10
  use prost::Message;
11
+ use std::collections::HashMap;
10
12
  use std::{
11
13
  cell::RefCell,
12
14
  fmt::Display,
@@ -50,6 +52,14 @@ enum Request {
50
52
  CreateClient {
51
53
  runtime: Arc<RuntimeHandle>,
52
54
  options: ClientOptions,
55
+ headers: Option<HashMap<String, String>>,
56
+ /// Used to send the result back into JS
57
+ callback: Root<JsFunction>,
58
+ },
59
+ /// A request to update a client's HTTP request headers
60
+ UpdateClientHeaders {
61
+ client: Arc<RawClient>,
62
+ headers: HashMap<String, String>,
53
63
  /// Used to send the result back into JS
54
64
  callback: Root<JsFunction>,
55
65
  },
@@ -271,13 +281,17 @@ fn start_bridge_loop(event_queue: Arc<EventQueue>, receiver: &mut UnboundedRecei
271
281
  Request::CreateClient {
272
282
  runtime,
273
283
  options,
284
+ headers,
274
285
  callback,
275
286
  } => {
276
287
  // `metrics_meter` (second arg) can be None here since we don't use the
277
288
  // returned client directly at the moment, when we repurpose the client to be
278
289
  // used by a Worker, `init_worker` will attach the correct metrics meter for
279
290
  // us.
280
- match options.connect_no_namespace(None).await {
291
+ match options
292
+ .connect_no_namespace(None, headers.map(|h| Arc::new(RwLock::new(h))))
293
+ .await
294
+ {
281
295
  Err(err) => {
282
296
  send_error(event_queue.clone(), callback, |cx| match err {
283
297
  ClientInitError::SystemInfoCallError(e) => TRANSPORT_ERROR
@@ -300,6 +314,14 @@ fn start_bridge_loop(event_queue: Arc<EventQueue>, receiver: &mut UnboundedRecei
300
314
  }
301
315
  }
302
316
  }
317
+ Request::UpdateClientHeaders {
318
+ client,
319
+ headers,
320
+ callback,
321
+ } => {
322
+ client.get_client().set_headers(headers);
323
+ send_result(event_queue.clone(), callback, |cx| Ok(cx.undefined()));
324
+ }
303
325
  Request::PollLogs { callback } => {
304
326
  let logs = fetch_global_buffered_logs();
305
327
  send_result(event_queue.clone(), callback, |cx| {
@@ -568,12 +590,28 @@ fn runtime_new(mut cx: FunctionContext) -> JsResult<BoxedRuntime> {
568
590
  /// Client will be returned in the supplied `callback`.
569
591
  fn client_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
570
592
  let runtime = cx.argument::<BoxedRuntime>(0)?;
571
- let client_options = cx.argument::<JsObject>(1)?.as_client_options(&mut cx)?;
593
+ let opts = cx.argument::<JsObject>(1)?;
572
594
  let callback = cx.argument::<JsFunction>(2)?;
573
595
 
596
+ let client_options = opts.as_client_options(&mut cx)?;
597
+ let headers = match js_optional_getter!(&mut cx, &opts, "headers", JsObject) {
598
+ None => None,
599
+ Some(h) => Some(
600
+ h.as_hash_map_of_string_to_string(&mut cx)
601
+ .map_err(|reason| {
602
+ cx.throw_type_error::<_, HashMap<String, String>>(format!(
603
+ "Invalid headers: {}",
604
+ reason
605
+ ))
606
+ .unwrap_err()
607
+ })?,
608
+ ),
609
+ };
610
+
574
611
  let request = Request::CreateClient {
575
612
  runtime: (**runtime).clone(),
576
613
  options: client_options,
614
+ headers,
577
615
  callback: callback.root(&mut cx),
578
616
  };
579
617
  if let Err(err) = runtime.sender.send(request) {
@@ -583,6 +621,33 @@ fn client_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
583
621
  Ok(cx.undefined())
584
622
  }
585
623
 
624
+ /// Update a Client's HTTP request headers
625
+ fn client_update_headers(mut cx: FunctionContext) -> JsResult<JsUndefined> {
626
+ let client = cx.argument::<BoxedClient>(0)?;
627
+ let headers = cx
628
+ .argument::<JsObject>(1)?
629
+ .as_hash_map_of_string_to_string(&mut cx)?;
630
+ let callback = cx.argument::<JsFunction>(2)?;
631
+
632
+ match &*client.borrow() {
633
+ None => {
634
+ callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Client")?;
635
+ }
636
+ Some(client) => {
637
+ let request = Request::UpdateClientHeaders {
638
+ client: client.core_client.clone(),
639
+ headers,
640
+ callback: callback.root(&mut cx),
641
+ };
642
+ if let Err(err) = client.runtime.sender.send(request) {
643
+ callback_with_unexpected_error(&mut cx, callback, err)?;
644
+ };
645
+ }
646
+ }
647
+
648
+ Ok(cx.undefined())
649
+ }
650
+
586
651
  /// Create a new worker asynchronously.
587
652
  /// Worker uses the provided connection and returned to JS using supplied `callback`.
588
653
  fn worker_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
@@ -593,9 +658,7 @@ fn worker_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
593
658
  let config = worker_options.as_worker_config(&mut cx)?;
594
659
  match &*client.borrow() {
595
660
  None => {
596
- callback_with_error(&mut cx, callback, move |cx| {
597
- UNEXPECTED_ERROR.from_string(cx, "Tried to use closed Client".to_string())
598
- })?;
661
+ callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Client")?;
599
662
  }
600
663
  Some(client) => {
601
664
  let request = Request::InitWorker {
@@ -843,6 +906,7 @@ fn main(mut cx: ModuleContext) -> NeonResult<()> {
843
906
  cx.export_function("initTelemetry", init_telemetry)?;
844
907
  cx.export_function("newRuntime", runtime_new)?;
845
908
  cx.export_function("newClient", client_new)?;
909
+ cx.export_function("clientUpdateHeaders", client_update_headers)?;
846
910
  cx.export_function("newWorker", worker_new)?;
847
911
  cx.export_function("newReplayWorker", replay_worker_new)?;
848
912
  cx.export_function("workerShutdown", worker_shutdown)?;