@temporalio/core-bridge 1.11.2 → 1.11.4

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 (119) hide show
  1. package/Cargo.lock +396 -489
  2. package/Cargo.toml +3 -2
  3. package/lib/errors.d.ts +2 -0
  4. package/lib/errors.js +7 -3
  5. package/lib/errors.js.map +1 -1
  6. package/lib/index.d.ts +8 -2
  7. package/lib/index.js.map +1 -1
  8. package/lib/worker-tuner.d.ts +111 -1
  9. package/package.json +3 -3
  10. package/releases/aarch64-apple-darwin/index.node +0 -0
  11. package/releases/aarch64-unknown-linux-gnu/index.node +0 -0
  12. package/releases/x86_64-apple-darwin/index.node +0 -0
  13. package/releases/x86_64-pc-windows-msvc/index.node +0 -0
  14. package/releases/x86_64-unknown-linux-gnu/index.node +0 -0
  15. package/sdk-core/.github/workflows/per-pr.yml +3 -3
  16. package/sdk-core/Cargo.toml +0 -1
  17. package/sdk-core/client/Cargo.toml +1 -2
  18. package/sdk-core/client/src/lib.rs +21 -13
  19. package/sdk-core/client/src/metrics.rs +1 -1
  20. package/sdk-core/client/src/raw.rs +46 -1
  21. package/sdk-core/core/Cargo.toml +7 -7
  22. package/sdk-core/core/benches/workflow_replay.rs +1 -1
  23. package/sdk-core/core/src/abstractions/take_cell.rs +1 -1
  24. package/sdk-core/core/src/abstractions.rs +98 -10
  25. package/sdk-core/core/src/core_tests/activity_tasks.rs +8 -2
  26. package/sdk-core/core/src/core_tests/local_activities.rs +1 -1
  27. package/sdk-core/core/src/core_tests/mod.rs +3 -3
  28. package/sdk-core/core/src/core_tests/updates.rs +104 -9
  29. package/sdk-core/core/src/core_tests/workers.rs +72 -3
  30. package/sdk-core/core/src/core_tests/workflow_tasks.rs +6 -7
  31. package/sdk-core/core/src/debug_client.rs +78 -0
  32. package/sdk-core/core/src/ephemeral_server/mod.rs +15 -5
  33. package/sdk-core/core/src/lib.rs +30 -4
  34. package/sdk-core/core/src/pollers/mod.rs +1 -1
  35. package/sdk-core/core/src/pollers/poll_buffer.rs +7 -7
  36. package/sdk-core/core/src/replay/mod.rs +4 -4
  37. package/sdk-core/core/src/telemetry/log_export.rs +2 -2
  38. package/sdk-core/core/src/telemetry/metrics.rs +69 -1
  39. package/sdk-core/core/src/telemetry/otel.rs +2 -2
  40. package/sdk-core/core/src/test_help/mod.rs +3 -3
  41. package/sdk-core/core/src/worker/activities/activity_heartbeat_manager.rs +3 -3
  42. package/sdk-core/core/src/worker/activities/activity_task_poller_stream.rs +1 -1
  43. package/sdk-core/core/src/worker/activities/local_activities.rs +68 -24
  44. package/sdk-core/core/src/worker/activities.rs +26 -15
  45. package/sdk-core/core/src/worker/client/mocks.rs +10 -4
  46. package/sdk-core/core/src/worker/client.rs +17 -0
  47. package/sdk-core/core/src/worker/mod.rs +71 -13
  48. package/sdk-core/core/src/worker/slot_provider.rs +5 -7
  49. package/sdk-core/core/src/worker/tuner/fixed_size.rs +4 -3
  50. package/sdk-core/core/src/worker/tuner/resource_based.rs +171 -32
  51. package/sdk-core/core/src/worker/tuner.rs +18 -6
  52. package/sdk-core/core/src/worker/workflow/history_update.rs +43 -13
  53. package/sdk-core/core/src/worker/workflow/machines/transition_coverage.rs +6 -6
  54. package/sdk-core/core/src/worker/workflow/machines/workflow_machines.rs +6 -5
  55. package/sdk-core/core/src/worker/workflow/managed_run.rs +3 -3
  56. package/sdk-core/core/src/worker/workflow/mod.rs +13 -7
  57. package/sdk-core/core/src/worker/workflow/wft_extraction.rs +7 -7
  58. package/sdk-core/core/src/worker/workflow/wft_poller.rs +2 -2
  59. package/sdk-core/core/src/worker/workflow/workflow_stream.rs +11 -11
  60. package/sdk-core/core-api/Cargo.toml +1 -0
  61. package/sdk-core/core-api/src/worker.rs +84 -30
  62. package/sdk-core/sdk/Cargo.toml +1 -2
  63. package/sdk-core/sdk/src/lib.rs +1 -1
  64. package/sdk-core/sdk/src/workflow_context.rs +9 -8
  65. package/sdk-core/sdk/src/workflow_future.rs +19 -14
  66. package/sdk-core/sdk-core-protos/Cargo.toml +2 -0
  67. package/sdk-core/sdk-core-protos/build.rs +6 -1
  68. package/sdk-core/sdk-core-protos/protos/api_upstream/buf.yaml +1 -0
  69. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv2.json +3207 -158
  70. package/sdk-core/sdk-core-protos/protos/api_upstream/openapi/openapiv3.yaml +2934 -118
  71. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/activity/v1/message.proto +67 -0
  72. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/common/v1/message.proto +47 -1
  73. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/event_type.proto +6 -7
  74. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/failed_cause.proto +2 -0
  75. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/reset.proto +5 -3
  76. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/task_queue.proto +3 -3
  77. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/enums/v1/update.proto +14 -13
  78. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/failure/v1/message.proto +1 -3
  79. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/history/v1/message.proto +22 -0
  80. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/nexus/v1/message.proto +13 -2
  81. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/operatorservice/v1/service.proto +26 -6
  82. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/schedule/v1/message.proto +5 -0
  83. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/sdk/v1/workflow_metadata.proto +6 -0
  84. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/taskqueue/v1/message.proto +46 -12
  85. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/update/v1/message.proto +18 -19
  86. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflow/v1/message.proto +27 -0
  87. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/request_response.proto +192 -19
  88. package/sdk-core/sdk-core-protos/protos/api_upstream/temporal/api/workflowservice/v1/service.proto +279 -12
  89. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/activity_result/activity_result.proto +1 -1
  90. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/activity_task/activity_task.proto +1 -1
  91. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/child_workflow/child_workflow.proto +1 -1
  92. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/common/common.proto +1 -1
  93. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/core_interface.proto +17 -1
  94. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/external_data/external_data.proto +1 -1
  95. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_activation/workflow_activation.proto +1 -1
  96. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_commands/workflow_commands.proto +1 -1
  97. package/sdk-core/sdk-core-protos/protos/local/temporal/sdk/core/workflow_completion/workflow_completion.proto +1 -1
  98. package/sdk-core/sdk-core-protos/src/lib.rs +30 -6
  99. package/sdk-core/test-utils/Cargo.toml +1 -2
  100. package/sdk-core/test-utils/src/lib.rs +2 -2
  101. package/sdk-core/tests/heavy_tests.rs +1 -1
  102. package/sdk-core/tests/integ_tests/ephemeral_server_tests.rs +2 -2
  103. package/sdk-core/tests/integ_tests/metrics_tests.rs +144 -7
  104. package/sdk-core/tests/integ_tests/queries_tests.rs +1 -1
  105. package/sdk-core/tests/integ_tests/update_tests.rs +109 -5
  106. package/sdk-core/tests/integ_tests/worker_tests.rs +44 -8
  107. package/sdk-core/tests/integ_tests/workflow_tests/local_activities.rs +1 -1
  108. package/sdk-core/tests/integ_tests/workflow_tests/resets.rs +1 -1
  109. package/sdk-core/tests/integ_tests/workflow_tests/signals.rs +1 -1
  110. package/sdk-core/tests/integ_tests/workflow_tests.rs +3 -2
  111. package/src/conversions/slot_supplier_bridge.rs +287 -0
  112. package/src/conversions.rs +23 -15
  113. package/src/helpers.rs +35 -1
  114. package/src/runtime.rs +7 -3
  115. package/src/worker.rs +1 -1
  116. package/ts/errors.ts +9 -2
  117. package/ts/index.ts +19 -4
  118. package/ts/worker-tuner.ts +123 -1
  119. package/sdk-core/sdk-core-protos/protos/api_upstream/.gitmodules +0 -3
@@ -0,0 +1,287 @@
1
+ use crate::helpers::{get_optional, js_getter};
2
+ use log::{error, warn};
3
+ use neon::types::JsNull;
4
+ use neon::{
5
+ context::Context,
6
+ context::FunctionContext,
7
+ event::Channel,
8
+ handle::{Handle, Root},
9
+ object::Object,
10
+ prelude::{JsBuffer, JsFunction, JsObject, JsPromise, JsUndefined, JsValue, NeonResult, Value},
11
+ };
12
+ use prost::Message;
13
+ use std::{cell::RefCell, marker::PhantomData, sync::Arc, time::Duration};
14
+ use temporal_sdk_core::api::worker::{
15
+ SlotKind, SlotKindType, SlotMarkUsedContext, SlotReleaseContext, SlotReservationContext,
16
+ SlotSupplier, SlotSupplierPermit,
17
+ };
18
+ use tokio::sync::oneshot;
19
+
20
+ pub struct SlotSupplierBridge<SK> {
21
+ inner: Arc<Root<JsObject>>,
22
+ reserve_cb: Arc<Root<JsFunction>>,
23
+ try_reserve_cb: Arc<Root<JsFunction>>,
24
+ mark_used_cb: Arc<Root<JsFunction>>,
25
+ release_cb: Arc<Root<JsFunction>>,
26
+ channel: Channel,
27
+ _kind: PhantomData<SK>,
28
+ }
29
+
30
+ impl<SK> SlotSupplierBridge<SK> {
31
+ pub(crate) fn new(cx: &mut FunctionContext, obj: Handle<'_, JsObject>) -> NeonResult<Self> {
32
+ Ok(Self {
33
+ inner: Arc::new(obj.root(cx)),
34
+ // Callbacks for each function are cached to reduce calling overhead
35
+ reserve_cb: Arc::new(js_getter!(cx, &obj, "reserveSlot", JsFunction).root(cx)),
36
+ try_reserve_cb: Arc::new(js_getter!(cx, &obj, "tryReserveSlot", JsFunction).root(cx)),
37
+ mark_used_cb: Arc::new(js_getter!(cx, &obj, "markSlotUsed", JsFunction).root(cx)),
38
+ release_cb: Arc::new(js_getter!(cx, &obj, "releaseSlot", JsFunction).root(cx)),
39
+ channel: cx.channel(),
40
+ _kind: PhantomData,
41
+ })
42
+ }
43
+ }
44
+
45
+ struct BridgePermitData {
46
+ permit: Arc<Root<JsObject>>,
47
+ }
48
+
49
+ struct CallAbortOnDrop {
50
+ chan: Channel,
51
+ aborter: oneshot::Receiver<Root<JsFunction>>,
52
+ }
53
+
54
+ impl Drop for CallAbortOnDrop {
55
+ fn drop(&mut self) {
56
+ if let Ok(aborter) = self.aborter.try_recv() {
57
+ let _ = self.chan.try_send(move |mut cx| {
58
+ let cb = aborter.to_inner(&mut cx);
59
+ let this = cx.undefined();
60
+ let _ = cb.call(&mut cx, this, []);
61
+ Ok(())
62
+ });
63
+ }
64
+ }
65
+ }
66
+
67
+ static PERMIT_DATA_FIELD: &str = "permit_data";
68
+
69
+ #[async_trait::async_trait]
70
+ impl<SK: SlotKind + Send + Sync> SlotSupplier for SlotSupplierBridge<SK> {
71
+ type SlotKind = SK;
72
+
73
+ async fn reserve_slot(&self, ctx: &dyn SlotReservationContext) -> SlotSupplierPermit {
74
+ loop {
75
+ let inner = self.inner.clone();
76
+ let rcb = self.reserve_cb.clone();
77
+ let task_queue = ctx.task_queue().to_string();
78
+ let worker_identity = ctx.worker_identity().to_string();
79
+ let worker_build_id = ctx.worker_build_id().to_string();
80
+ let is_sticky = ctx.is_sticky();
81
+
82
+ let (callback_fut, _abort_on_drop) = match self
83
+ .channel
84
+ .send(move |mut cx| {
85
+ let context = Self::mk_reserve_ctx(
86
+ task_queue,
87
+ worker_identity,
88
+ worker_build_id,
89
+ is_sticky,
90
+ &mut cx,
91
+ )?;
92
+ let (aborter_tx, aborter) = oneshot::channel();
93
+ let abort_on_drop = CallAbortOnDrop {
94
+ chan: cx.channel(),
95
+ aborter,
96
+ };
97
+ let aborter_tx = RefCell::new(Some(aborter_tx));
98
+ let abort_func = JsFunction::new(&mut cx, move |mut cx| {
99
+ let func: Handle<JsFunction> = cx.argument(0)?;
100
+ if let Some(aborter_tx) = aborter_tx.take() {
101
+ let _ = aborter_tx.send(func.root(&mut cx));
102
+ }
103
+ Ok(cx.undefined())
104
+ })?
105
+ .upcast();
106
+
107
+ let this = (*inner).clone(&mut cx).into_inner(&mut cx);
108
+ let val = rcb
109
+ .to_inner(&mut cx)
110
+ .call(&mut cx, this, [context, abort_func])?;
111
+ let as_prom = val.downcast_or_throw::<JsPromise, _>(&mut cx)?;
112
+ let fut = as_prom.to_future(&mut cx, |mut cx, result| match result {
113
+ Ok(value) => {
114
+ let permit_obj = JsObject::new(&mut cx);
115
+ permit_obj.set(&mut cx, PERMIT_DATA_FIELD, value)?;
116
+ Ok(Ok(permit_obj.root(&mut cx)))
117
+ }
118
+ Err(_) => Ok(Err(())),
119
+ })?;
120
+ Ok((fut, abort_on_drop))
121
+ })
122
+ .await
123
+ {
124
+ Ok(v) => v,
125
+ Err(e) => {
126
+ warn!("Error reserving slot: {:?}", e);
127
+ continue;
128
+ }
129
+ };
130
+
131
+ match callback_fut.await {
132
+ Ok(Ok(res)) => {
133
+ let permit = SlotSupplierPermit::with_user_data(BridgePermitData {
134
+ permit: Arc::new(res),
135
+ });
136
+ return permit;
137
+ }
138
+ // Error in user function
139
+ Ok(Err(())) => {
140
+ // Nothing to do here. Error in user's function (or an abort).
141
+ // Logging handled on the JS side.
142
+ }
143
+ Err(e) => {
144
+ error!(
145
+ "There was an error in the rust/node bridge while reserving a slot: {}",
146
+ e
147
+ );
148
+ }
149
+ }
150
+ // Wait a beat to avoid spamming errors
151
+ tokio::time::sleep(Duration::from_millis(1000)).await;
152
+ }
153
+ }
154
+
155
+ fn try_reserve_slot(&self, ctx: &dyn SlotReservationContext) -> Option<SlotSupplierPermit> {
156
+ let inner = self.inner.clone();
157
+ let rcb = self.try_reserve_cb.clone();
158
+ let task_queue = ctx.task_queue().to_string();
159
+ let worker_identity = ctx.worker_identity().to_string();
160
+ let worker_build_id = ctx.worker_build_id().to_string();
161
+ let is_sticky = ctx.is_sticky();
162
+
163
+ // This is... unfortunate but since this method is called from an async context way up
164
+ // the stack, but is not async itself AND we need some way to get the result from the JS
165
+ // callback, we must use this roundabout way of blocking. Simply calling `join` on the
166
+ // channel send won't work - it'll panic because it calls block_on internally.
167
+ let runtime_handle = tokio::runtime::Handle::current();
168
+ let _entered = runtime_handle.enter();
169
+ let callback_res = futures::executor::block_on(self.channel.send(move |mut cx| {
170
+ let context = Self::mk_reserve_ctx(
171
+ task_queue,
172
+ worker_identity,
173
+ worker_build_id,
174
+ is_sticky,
175
+ &mut cx,
176
+ )?;
177
+
178
+ let this = (*inner).clone(&mut cx).into_inner(&mut cx);
179
+ let val = rcb.to_inner(&mut cx).call(&mut cx, this, [context])?;
180
+ if val.is_a::<JsUndefined, _>(&mut cx) || val.is_a::<JsNull, _>(&mut cx) {
181
+ return Ok(None);
182
+ }
183
+ let permit_obj = JsObject::new(&mut cx);
184
+ permit_obj.set(&mut cx, PERMIT_DATA_FIELD, val)?;
185
+ Ok(Some(permit_obj.root(&mut cx)))
186
+ }));
187
+
188
+ // Ignore errors, they'll be logged by JS
189
+ callback_res.ok().flatten().map(|res| {
190
+ SlotSupplierPermit::with_user_data(BridgePermitData {
191
+ permit: Arc::new(res),
192
+ })
193
+ })
194
+ }
195
+
196
+ fn mark_slot_used(&self, ctx: &dyn SlotMarkUsedContext<SlotKind = Self::SlotKind>) {
197
+ let inner = self.inner.clone();
198
+ let cb = self.mark_used_cb.clone();
199
+ let permit_data = ctx
200
+ .permit()
201
+ .user_data::<BridgePermitData>()
202
+ .map(|d| d.permit.clone());
203
+ // Get the slot info as bytes
204
+ let slot_info_bytes = ctx.info().encode_to_vec();
205
+
206
+ self.channel.send(move |mut cx| {
207
+ let context = JsObject::new(&mut cx);
208
+ if let Some(permit_obj) = permit_data {
209
+ let ph: Handle<JsObject> = permit_obj.to_inner(&mut cx);
210
+ let pd = ph.get_value(&mut cx, PERMIT_DATA_FIELD)?;
211
+ context.set(&mut cx, "permit", pd)?;
212
+ }
213
+ let slot_info = JsBuffer::from_slice(&mut cx, &slot_info_bytes)?;
214
+ context.set(&mut cx, "slotInfo", slot_info)?;
215
+ let context = context.as_value(&mut cx);
216
+
217
+ let this = (*inner).clone(&mut cx).into_inner(&mut cx);
218
+ let val = cb.to_inner(&mut cx).call(&mut cx, this, [context])?;
219
+ if val.is_a::<JsUndefined, _>(&mut cx) {
220
+ return Ok(None);
221
+ }
222
+ let as_obj = val.downcast_or_throw::<JsObject, _>(&mut cx)?;
223
+ Ok(Some(as_obj.root(&mut cx)))
224
+ });
225
+ }
226
+
227
+ fn release_slot(&self, ctx: &dyn SlotReleaseContext<SlotKind = Self::SlotKind>) {
228
+ let inner = self.inner.clone();
229
+ let cb = self.release_cb.clone();
230
+ let permit_data = ctx
231
+ .permit()
232
+ .user_data::<BridgePermitData>()
233
+ .map(|d| d.permit.clone());
234
+ // Get the slot info as bytes
235
+ let slot_info_bytes = ctx.info().map(|m| m.encode_to_vec());
236
+
237
+ self.channel.send(move |mut cx| {
238
+ let context = JsObject::new(&mut cx);
239
+ if let Some(permit_obj) = permit_data {
240
+ let ph: Handle<JsObject> = permit_obj.to_inner(&mut cx);
241
+ let pd = ph.get_value(&mut cx, PERMIT_DATA_FIELD)?;
242
+ context.set(&mut cx, "permit", pd)?;
243
+ }
244
+ if let Some(slot_info_bytes) = slot_info_bytes {
245
+ let slot_info = JsBuffer::from_slice(&mut cx, &slot_info_bytes)?;
246
+ context.set(&mut cx, "slotInfo", slot_info)?;
247
+ }
248
+ let context = context.as_value(&mut cx);
249
+
250
+ let this = (*inner).clone(&mut cx).into_inner(&mut cx);
251
+ let val = cb.to_inner(&mut cx).call(&mut cx, this, [context])?;
252
+ if val.is_a::<JsUndefined, _>(&mut cx) {
253
+ return Ok(None);
254
+ }
255
+ let as_obj = val.downcast_or_throw::<JsObject, _>(&mut cx)?;
256
+ Ok(Some(as_obj.root(&mut cx)))
257
+ });
258
+ }
259
+ }
260
+
261
+ impl<SK: SlotKind> SlotSupplierBridge<SK> {
262
+ fn mk_reserve_ctx<'a, C: Context<'a>>(
263
+ task_queue: String,
264
+ worker_identity: String,
265
+ worker_build_id: String,
266
+ is_sticky: bool,
267
+ cx: &mut C,
268
+ ) -> NeonResult<Handle<'a, JsValue>> {
269
+ let context = JsObject::new(cx);
270
+ let slottype = cx.string(match SK::kind() {
271
+ SlotKindType::Workflow => "workflow",
272
+ SlotKindType::Activity => "activity",
273
+ SlotKindType::LocalActivity => "local-activity",
274
+ });
275
+ context.set(cx, "slotType", slottype)?;
276
+ let tq = cx.string(task_queue);
277
+ context.set(cx, "taskQueue", tq)?;
278
+ let wid = cx.string(worker_identity);
279
+ context.set(cx, "workerIdentity", wid)?;
280
+ let bid = cx.string(worker_build_id);
281
+ context.set(cx, "workerBuildId", bid)?;
282
+ let is_sticky = cx.boolean(is_sticky);
283
+ context.set(cx, "isSticky", is_sticky)?;
284
+ let context = context.as_value(cx);
285
+ Ok(context)
286
+ }
287
+ }
@@ -5,8 +5,10 @@ use neon::{
5
5
  prelude::*,
6
6
  types::{JsBoolean, JsNumber, JsString},
7
7
  };
8
+ use slot_supplier_bridge::SlotSupplierBridge;
8
9
  use std::{collections::HashMap, net::SocketAddr, sync::Arc, time::Duration};
9
10
  use temporal_client::HttpConnectProxyOptions;
11
+ use temporal_sdk_core::api::worker::SlotKind;
10
12
  use temporal_sdk_core::{
11
13
  api::telemetry::{Logger, MetricTemporality, TelemetryOptions, TelemetryOptionsBuilder},
12
14
  api::{
@@ -25,6 +27,8 @@ use temporal_sdk_core::{
25
27
  TlsConfig, TunerHolderOptionsBuilder, Url,
26
28
  };
27
29
 
30
+ mod slot_supplier_bridge;
31
+
28
32
  pub enum EphemeralServerConfig {
29
33
  TestServer(TestServerConfig),
30
34
  DevServer(TemporalDevServerConfig),
@@ -65,11 +69,11 @@ pub(crate) trait ObjectHandleConversionsExt {
65
69
  &self,
66
70
  cx: &mut FunctionContext,
67
71
  ) -> NeonResult<HashMap<String, String>>;
68
- fn as_slot_supplier(
69
- &self,
72
+ fn into_slot_supplier<SK: SlotKind + Send + Sync + 'static>(
73
+ self,
70
74
  cx: &mut FunctionContext,
71
75
  rbo: &mut Option<ResourceBasedSlotsOptions>,
72
- ) -> NeonResult<SlotSupplierOptions>;
76
+ ) -> NeonResult<SlotSupplierOptions<SK>>;
73
77
  }
74
78
 
75
79
  impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
@@ -409,18 +413,18 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
409
413
  if let Some(wf_slot_supp) =
410
414
  js_optional_getter!(cx, &tuner, "workflowTaskSlotSupplier", JsObject)
411
415
  {
412
- tuner_holder.workflow_slot_options(wf_slot_supp.as_slot_supplier(cx, &mut rbo)?);
416
+ tuner_holder.workflow_slot_options(wf_slot_supp.into_slot_supplier(cx, &mut rbo)?);
413
417
  }
414
418
  if let Some(act_slot_supp) =
415
419
  js_optional_getter!(cx, &tuner, "activityTaskSlotSupplier", JsObject)
416
420
  {
417
- tuner_holder.activity_slot_options(act_slot_supp.as_slot_supplier(cx, &mut rbo)?);
421
+ tuner_holder.activity_slot_options(act_slot_supp.into_slot_supplier(cx, &mut rbo)?);
418
422
  }
419
423
  if let Some(local_act_slot_supp) =
420
424
  js_optional_getter!(cx, &tuner, "localActivityTaskSlotSupplier", JsObject)
421
425
  {
422
426
  tuner_holder.local_activity_slot_options(
423
- local_act_slot_supp.as_slot_supplier(cx, &mut rbo)?,
427
+ local_act_slot_supp.into_slot_supplier(cx, &mut rbo)?,
424
428
  );
425
429
  }
426
430
  if let Some(rbo) = rbo {
@@ -567,20 +571,20 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
567
571
  }
568
572
  }
569
573
 
570
- fn as_slot_supplier(
571
- &self,
574
+ fn into_slot_supplier<SK: SlotKind + Send + Sync + 'static>(
575
+ self,
572
576
  cx: &mut FunctionContext,
573
577
  rbo: &mut Option<ResourceBasedSlotsOptions>,
574
- ) -> NeonResult<SlotSupplierOptions> {
575
- match js_value_getter!(cx, self, "type", JsString).as_str() {
578
+ ) -> NeonResult<SlotSupplierOptions<SK>> {
579
+ match js_value_getter!(cx, &self, "type", JsString).as_str() {
576
580
  "fixed-size" => Ok(SlotSupplierOptions::FixedSize {
577
- slots: js_value_getter!(cx, self, "numSlots", JsNumber) as usize,
581
+ slots: js_value_getter!(cx, &self, "numSlots", JsNumber) as usize,
578
582
  }),
579
583
  "resource-based" => {
580
- let min_slots = js_value_getter!(cx, self, "minimumSlots", JsNumber);
581
- let max_slots = js_value_getter!(cx, self, "maximumSlots", JsNumber);
582
- let ramp_throttle = js_value_getter!(cx, self, "rampThrottleMs", JsNumber) as u64;
583
- if let Some(tuner_opts) = js_optional_getter!(cx, self, "tunerOptions", JsObject) {
584
+ let min_slots = js_value_getter!(cx, &self, "minimumSlots", JsNumber);
585
+ let max_slots = js_value_getter!(cx, &self, "maximumSlots", JsNumber);
586
+ let ramp_throttle = js_value_getter!(cx, &self, "rampThrottleMs", JsNumber) as u64;
587
+ if let Some(tuner_opts) = js_optional_getter!(cx, &self, "tunerOptions", JsObject) {
584
588
  let target_mem =
585
589
  js_value_getter!(cx, &tuner_opts, "targetMemoryUsage", JsNumber);
586
590
  let target_cpu = js_value_getter!(cx, &tuner_opts, "targetCpuUsage", JsNumber);
@@ -603,6 +607,10 @@ impl ObjectHandleConversionsExt for Handle<'_, JsObject> {
603
607
  ),
604
608
  ))
605
609
  }
610
+ "custom" => {
611
+ let ssb = SlotSupplierBridge::new(cx, self)?;
612
+ Ok(SlotSupplierOptions::Custom(Arc::new(ssb)))
613
+ }
606
614
  _ => cx.throw_type_error("Invalid slot supplier type"),
607
615
  }
608
616
  }
package/src/helpers.rs CHANGED
@@ -106,6 +106,23 @@ macro_rules! js_optional_getter {
106
106
 
107
107
  pub(crate) use js_optional_getter;
108
108
 
109
+ macro_rules! js_getter {
110
+ ($js_cx:expr, $js_obj:expr, $prop_name:expr, $js_type:ty) => {
111
+ match get_optional($js_cx, $js_obj, $prop_name) {
112
+ None => $js_cx.throw_type_error(format!("{} must be defined", $prop_name))?,
113
+ Some(val) => {
114
+ if val.is_a::<$js_type, _>($js_cx) {
115
+ val.downcast_or_throw::<$js_type, _>($js_cx)?
116
+ } else {
117
+ $js_cx.throw_type_error(format!("Invalid {}", $prop_name))?
118
+ }
119
+ }
120
+ }
121
+ };
122
+ }
123
+
124
+ pub(crate) use js_getter;
125
+
109
126
  macro_rules! js_optional_value_getter {
110
127
  ($js_cx:expr, $js_obj:expr, $prop_name:expr, $js_type:ty) => {
111
128
  js_optional_getter!($js_cx, $js_obj, $prop_name, $js_type).map(|v| v.value($js_cx))
@@ -199,7 +216,7 @@ pub fn serde_value_to_js_value<'a>(
199
216
  serde_json::Value::Bool(b) => Ok(cx.boolean(b).upcast()),
200
217
  serde_json::Value::Null => Ok(cx.null().upcast()),
201
218
  serde_json::Value::Array(vec) => {
202
- let arr: Handle<'a, JsArray> = JsArray::new(cx, vec.len() as u32);
219
+ let arr: Handle<'a, JsArray> = JsArray::new(cx, vec.len());
203
220
  for (i, v) in vec.into_iter().enumerate() {
204
221
  let v = serde_value_to_js_value(cx, v)?;
205
222
  arr.set(cx, i as u32, v)?;
@@ -247,6 +264,23 @@ fn snake_to_camel(input: String) -> String {
247
264
  }
248
265
  }
249
266
 
267
+ #[allow(dead_code)]
268
+ // Useful to help debug JSObject contents
269
+ pub fn log_js_object<'a, 'b, C: Context<'b>>(cx: &mut C, js_object: &Handle<'a, JsObject>) {
270
+ let global = cx.global_object();
271
+ let console = global
272
+ .get::<JsObject, _, _>(cx, "console")
273
+ .expect("Failed to get console object");
274
+
275
+ let log = console
276
+ .get::<JsFunction, _, _>(cx, "log")
277
+ .expect("Failed to get log function");
278
+
279
+ let args = vec![js_object.upcast()]; // Upcast js_object to JsValue
280
+ log.call(cx, console, args)
281
+ .expect("Failed to call console.log");
282
+ }
283
+
250
284
  #[cfg(test)]
251
285
  mod tests {
252
286
  use super::*;
package/src/runtime.rs CHANGED
@@ -14,7 +14,7 @@ use temporal_sdk_core::{
14
14
  ephemeral_server::EphemeralServer as CoreEphemeralServer,
15
15
  init_replay_worker, init_worker,
16
16
  replay::{HistoryForReplay, ReplayWorkerInput},
17
- ClientOptions, CoreRuntime, RetryClient, WorkerConfig,
17
+ ClientOptions, CoreRuntime, RetryClient, TokioRuntimeBuilder, WorkerConfig,
18
18
  };
19
19
  use tokio::sync::{
20
20
  mpsc::{channel, unbounded_channel, Sender, UnboundedReceiver, UnboundedSender},
@@ -126,6 +126,10 @@ pub fn start_bridge_loop(
126
126
  tokio_builder.enable_all().thread_name("core");
127
127
  let telem_opts = telemetry_options.0;
128
128
  let meter_maker = telemetry_options.1;
129
+ let tokio_builder: TokioRuntimeBuilder<Box<dyn Fn() + Send + Sync>> = TokioRuntimeBuilder {
130
+ inner: tokio_builder,
131
+ lang_on_thread_start: None,
132
+ };
129
133
  let mut core_runtime =
130
134
  CoreRuntime::new(telem_opts, tokio_builder).expect("Failed to create CoreRuntime");
131
135
 
@@ -254,7 +258,7 @@ pub fn start_bridge_loop(
254
258
  worker,
255
259
  channel,
256
260
  callback,
257
- None
261
+ None,
258
262
  ));
259
263
  }
260
264
  Err(err) => send_error(channel.clone(), callback, move |cx| {
@@ -274,7 +278,7 @@ pub fn start_bridge_loop(
274
278
  worker,
275
279
  channel.clone(),
276
280
  callback,
277
- Some(tunnel)
281
+ Some(tunnel),
278
282
  ));
279
283
  }
280
284
  Err(err) => send_error(channel.clone(), callback, move |cx| {
package/src/worker.rs CHANGED
@@ -226,12 +226,12 @@ pub fn worker_new(mut cx: FunctionContext) -> JsResult<JsUndefined> {
226
226
  let worker_options = cx.argument::<JsObject>(1)?;
227
227
  let callback = cx.argument::<JsFunction>(2)?;
228
228
 
229
- let config = worker_options.as_worker_config(&mut cx)?;
230
229
  match client.borrow().as_ref() {
231
230
  None => {
232
231
  callback_with_unexpected_error(&mut cx, callback, "Tried to use closed Client")?;
233
232
  }
234
233
  Some(client) => {
234
+ let config = worker_options.as_worker_config(&mut cx)?;
235
235
  let request = RuntimeRequest::InitWorker {
236
236
  client: client.core_client.clone(),
237
237
  config,
package/ts/errors.ts CHANGED
@@ -18,7 +18,14 @@ export class TransportError extends Error {}
18
18
  * Something unexpected happened, considered fatal
19
19
  */
20
20
  @SymbolBasedInstanceOfError('UnexpectedError')
21
- export class UnexpectedError extends Error {}
21
+ export class UnexpectedError extends Error {
22
+ constructor(
23
+ message: string,
24
+ public cause?: unknown
25
+ ) {
26
+ super(message);
27
+ }
28
+ }
22
29
 
23
30
  export { IllegalStateError };
24
31
 
@@ -47,7 +54,7 @@ export function convertFromNamedError(e: unknown, keepStackTrace: boolean): unkn
47
54
  return newerr;
48
55
 
49
56
  case 'UnexpectedError':
50
- newerr = new UnexpectedError(e.message);
57
+ newerr = new UnexpectedError(e.message, e);
51
58
  newerr.stack = keepStackTrace ? e.stack : undefined;
52
59
  return newerr;
53
60
  }
package/ts/index.ts CHANGED
@@ -8,6 +8,15 @@ export {
8
8
  ResourceBasedSlotOptions,
9
9
  ResourceBasedTunerOptions,
10
10
  FixedSizeSlotSupplier,
11
+ CustomSlotSupplier,
12
+ SlotInfo,
13
+ WorkflowSlotInfo,
14
+ ActivitySlotInfo,
15
+ LocalActivitySlotInfo,
16
+ SlotMarkUsedContext,
17
+ SlotPermit,
18
+ SlotReserveContext,
19
+ SlotReleaseContext,
11
20
  } from './worker-tuner';
12
21
 
13
22
  export type { TLSConfig, ProxyConfig, HttpConnectProxyConfig };
@@ -98,7 +107,7 @@ export interface ClientOptions {
98
107
  * @experimental
99
108
  */
100
109
  export interface ConsoleLogger {
101
- console: {}; // eslint-disable-line @typescript-eslint/ban-types
110
+ console: {}; // eslint-disable-line @typescript-eslint/no-empty-object-type
102
111
  }
103
112
 
104
113
  /**
@@ -134,7 +143,13 @@ export interface OtelCollectorExporter {
134
143
  /**
135
144
  * URL of a gRPC OpenTelemetry collector.
136
145
  *
137
- * @format Starts with "grpc://" or "http://" for an unsecured connection (typical), or "grpcs://" or "https://" for a TLS connection.
146
+ * Syntax should generally look like `http://server:4317` (the `grpc://` is also fine). Core's OTLP
147
+ * metric exporter does not support the 'OTLP/HTTP' protocol (e.g. `http://server:4318/v1/metrics`).
148
+ * For greater flexibility, you may setup an OTel collector running as a sidecar (e.g. to proxy
149
+ * OTLP/gRPC requests to a remote OTLP/HTTP endpoint).
150
+ *
151
+ * @format Starts with "grpc://" or "http://" for an unsecured connection (typical),
152
+ * or "grpcs://" or "https://" for a TLS connection.
138
153
  * @note The `OTEL_EXPORTER_OTLP_ENDPOINT` environment variable, if set, will override this property.
139
154
  */
140
155
  url: string;
@@ -269,8 +284,8 @@ export type CompiledTelemetryOptions = {
269
284
  logging: {
270
285
  filter: string;
271
286
  } & (
272
- | { console: {} /* eslint-disable-line @typescript-eslint/ban-types */ }
273
- | { forward: {} /* eslint-disable-line @typescript-eslint/ban-types */ }
287
+ | { console: {} /* eslint-disable-line @typescript-eslint/no-empty-object-type */ }
288
+ | { forward: {} /* eslint-disable-line @typescript-eslint/no-empty-object-type */ }
274
289
  );
275
290
  metrics?: {
276
291
  temporality?: 'cumulative' | 'delta';