@braintrust/temporal 0.1.0 → 0.1.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.
- package/README.md +6 -1
- package/dist/chunk-NKP2TK53.mjs +31 -0
- package/dist/index.d.mts +1 -2
- package/dist/index.d.ts +1 -2
- package/dist/index.js +15 -14
- package/dist/index.mjs +16 -18
- package/dist/workflow-interceptors.mjs +1 -1
- package/package.json +1 -1
- package/src/index.ts +3 -1
- package/src/plugin.ts +30 -25
- package/examples/temporal/.env.example +0 -2
- package/examples/temporal/Procfile +0 -4
- package/examples/temporal/README.md +0 -183
- package/examples/temporal/mise.toml +0 -40
- package/examples/temporal/package.json +0 -26
- package/examples/temporal/src/activities.ts +0 -42
- package/examples/temporal/src/client.ts +0 -53
- package/examples/temporal/src/worker.ts +0 -31
- package/examples/temporal/src/workflows.ts +0 -47
- package/examples/temporal/tsconfig.json +0 -15
- package/src/temporal.test.ts +0 -243
package/README.md
CHANGED
|
@@ -33,9 +33,14 @@ const client = new Client({
|
|
|
33
33
|
plugins: [plugin],
|
|
34
34
|
});
|
|
35
35
|
|
|
36
|
+
// ESM-safe resolution (recommended for ESM projects):
|
|
37
|
+
const workflowsUrl = new URL("./workflows", import.meta.url);
|
|
38
|
+
const workflowsPath = workflowsUrl.pathname;
|
|
39
|
+
// CommonJS resolution (existing/example usage):
|
|
40
|
+
// const workflowsPath = require.resolve("./workflows");
|
|
36
41
|
const worker = await Worker.create({
|
|
37
42
|
taskQueue: "my-queue",
|
|
38
|
-
workflowsPath:
|
|
43
|
+
workflowsPath: workflowsPath,
|
|
39
44
|
activities,
|
|
40
45
|
plugins: [plugin],
|
|
41
46
|
});
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// src/utils.ts
|
|
2
|
+
var BRAINTRUST_SPAN_HEADER = "_braintrust-span";
|
|
3
|
+
var BRAINTRUST_WORKFLOW_SPAN_HEADER = "_braintrust-workflow-span";
|
|
4
|
+
var BRAINTRUST_WORKFLOW_SPAN_ID_HEADER = "_braintrust-workflow-span-id";
|
|
5
|
+
function serializeHeaderValue(value) {
|
|
6
|
+
return {
|
|
7
|
+
metadata: {
|
|
8
|
+
encoding: new TextEncoder().encode("json/plain")
|
|
9
|
+
},
|
|
10
|
+
data: new TextEncoder().encode(JSON.stringify(value))
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
function deserializeHeaderValue(payload) {
|
|
14
|
+
if (!payload?.data) {
|
|
15
|
+
return void 0;
|
|
16
|
+
}
|
|
17
|
+
try {
|
|
18
|
+
const decoded = new TextDecoder().decode(payload.data);
|
|
19
|
+
return JSON.parse(decoded);
|
|
20
|
+
} catch {
|
|
21
|
+
return void 0;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export {
|
|
26
|
+
BRAINTRUST_SPAN_HEADER,
|
|
27
|
+
BRAINTRUST_WORKFLOW_SPAN_HEADER,
|
|
28
|
+
BRAINTRUST_WORKFLOW_SPAN_ID_HEADER,
|
|
29
|
+
serializeHeaderValue,
|
|
30
|
+
deserializeHeaderValue
|
|
31
|
+
};
|
package/dist/index.d.mts
CHANGED
|
@@ -43,8 +43,7 @@ declare class BraintrustTemporalPlugin implements ClientPlugin, WorkerPlugin {
|
|
|
43
43
|
configureClient(options: Omit<ClientOptions, "plugins">): Omit<ClientOptions, "plugins">;
|
|
44
44
|
/**
|
|
45
45
|
* Configure the Temporal Worker with Braintrust interceptors and sinks.
|
|
46
|
-
*
|
|
47
|
-
* and the workflow interceptor modules for bundling.
|
|
46
|
+
* Prepends interceptors to ensure they run first, making plugin order irrelevant.
|
|
48
47
|
*/
|
|
49
48
|
configureWorker(options: WorkerOptions): WorkerOptions;
|
|
50
49
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -43,8 +43,7 @@ declare class BraintrustTemporalPlugin implements ClientPlugin, WorkerPlugin {
|
|
|
43
43
|
configureClient(options: Omit<ClientOptions, "plugins">): Omit<ClientOptions, "plugins">;
|
|
44
44
|
/**
|
|
45
45
|
* Configure the Temporal Worker with Braintrust interceptors and sinks.
|
|
46
|
-
*
|
|
47
|
-
* and the workflow interceptor modules for bundling.
|
|
46
|
+
* Prepends interceptors to ensure they run first, making plugin order irrelevant.
|
|
48
47
|
*/
|
|
49
48
|
configureWorker(options: WorkerOptions): WorkerOptions;
|
|
50
49
|
}
|
package/dist/index.js
CHANGED
|
@@ -233,6 +233,7 @@ function createBraintrustActivityInterceptor(ctx) {
|
|
|
233
233
|
}
|
|
234
234
|
|
|
235
235
|
// src/plugin.ts
|
|
236
|
+
var WORKFLOW_INTERCEPTORS_SPEC = "@braintrust/temporal/workflow-interceptors";
|
|
236
237
|
var BraintrustTemporalPlugin = class {
|
|
237
238
|
get name() {
|
|
238
239
|
return "braintrust";
|
|
@@ -246,11 +247,14 @@ var BraintrustTemporalPlugin = class {
|
|
|
246
247
|
const braintrustInterceptor = createBraintrustClientInterceptor();
|
|
247
248
|
let workflow;
|
|
248
249
|
if (Array.isArray(existing)) {
|
|
249
|
-
workflow = [
|
|
250
|
+
workflow = [
|
|
251
|
+
...existing,
|
|
252
|
+
braintrustInterceptor
|
|
253
|
+
];
|
|
250
254
|
} else if (existing) {
|
|
251
255
|
workflow = {
|
|
252
256
|
...existing,
|
|
253
|
-
...braintrustInterceptor
|
|
257
|
+
calls: [...existing.calls ?? [], () => braintrustInterceptor]
|
|
254
258
|
};
|
|
255
259
|
} else {
|
|
256
260
|
workflow = [braintrustInterceptor];
|
|
@@ -265,28 +269,25 @@ var BraintrustTemporalPlugin = class {
|
|
|
265
269
|
}
|
|
266
270
|
/**
|
|
267
271
|
* Configure the Temporal Worker with Braintrust interceptors and sinks.
|
|
268
|
-
*
|
|
269
|
-
* and the workflow interceptor modules for bundling.
|
|
272
|
+
* Prepends interceptors to ensure they run first, making plugin order irrelevant.
|
|
270
273
|
*/
|
|
271
274
|
configureWorker(options) {
|
|
272
|
-
const existingActivityInterceptors = options.interceptors?.activity ?? [];
|
|
273
|
-
const existingWorkflowModules = options.interceptors?.workflowModules ?? [];
|
|
274
|
-
const existingSinks = options.sinks ?? {};
|
|
275
|
-
const braintrustSinks = createBraintrustSinks();
|
|
276
|
-
const workflowInterceptorsPath = require.resolve("@braintrust/temporal/workflow-interceptors");
|
|
277
275
|
return {
|
|
278
276
|
...options,
|
|
279
277
|
interceptors: {
|
|
280
278
|
...options.interceptors,
|
|
281
279
|
activity: [
|
|
282
|
-
|
|
283
|
-
|
|
280
|
+
createBraintrustActivityInterceptor,
|
|
281
|
+
...options.interceptors?.activity ?? []
|
|
284
282
|
],
|
|
285
|
-
workflowModules: [
|
|
283
|
+
workflowModules: [
|
|
284
|
+
WORKFLOW_INTERCEPTORS_SPEC,
|
|
285
|
+
...options.interceptors?.workflowModules ?? []
|
|
286
|
+
]
|
|
286
287
|
},
|
|
287
288
|
sinks: {
|
|
288
|
-
...
|
|
289
|
-
...
|
|
289
|
+
...createBraintrustSinks(),
|
|
290
|
+
...options.sinks ?? {}
|
|
290
291
|
}
|
|
291
292
|
};
|
|
292
293
|
}
|
package/dist/index.mjs
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BRAINTRUST_SPAN_HEADER,
|
|
3
3
|
BRAINTRUST_WORKFLOW_SPAN_ID_HEADER,
|
|
4
|
-
__require,
|
|
5
4
|
deserializeHeaderValue
|
|
6
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-NKP2TK53.mjs";
|
|
7
6
|
|
|
8
7
|
// src/interceptors.ts
|
|
9
8
|
import { defaultPayloadConverter } from "@temporalio/common";
|
|
@@ -188,6 +187,7 @@ function createBraintrustActivityInterceptor(ctx) {
|
|
|
188
187
|
}
|
|
189
188
|
|
|
190
189
|
// src/plugin.ts
|
|
190
|
+
var WORKFLOW_INTERCEPTORS_SPEC = "@braintrust/temporal/workflow-interceptors";
|
|
191
191
|
var BraintrustTemporalPlugin = class {
|
|
192
192
|
get name() {
|
|
193
193
|
return "braintrust";
|
|
@@ -201,11 +201,14 @@ var BraintrustTemporalPlugin = class {
|
|
|
201
201
|
const braintrustInterceptor = createBraintrustClientInterceptor();
|
|
202
202
|
let workflow;
|
|
203
203
|
if (Array.isArray(existing)) {
|
|
204
|
-
workflow = [
|
|
204
|
+
workflow = [
|
|
205
|
+
...existing,
|
|
206
|
+
braintrustInterceptor
|
|
207
|
+
];
|
|
205
208
|
} else if (existing) {
|
|
206
209
|
workflow = {
|
|
207
210
|
...existing,
|
|
208
|
-
...braintrustInterceptor
|
|
211
|
+
calls: [...existing.calls ?? [], () => braintrustInterceptor]
|
|
209
212
|
};
|
|
210
213
|
} else {
|
|
211
214
|
workflow = [braintrustInterceptor];
|
|
@@ -220,30 +223,25 @@ var BraintrustTemporalPlugin = class {
|
|
|
220
223
|
}
|
|
221
224
|
/**
|
|
222
225
|
* Configure the Temporal Worker with Braintrust interceptors and sinks.
|
|
223
|
-
*
|
|
224
|
-
* and the workflow interceptor modules for bundling.
|
|
226
|
+
* Prepends interceptors to ensure they run first, making plugin order irrelevant.
|
|
225
227
|
*/
|
|
226
228
|
configureWorker(options) {
|
|
227
|
-
const existingActivityInterceptors = options.interceptors?.activity ?? [];
|
|
228
|
-
const existingWorkflowModules = options.interceptors?.workflowModules ?? [];
|
|
229
|
-
const existingSinks = options.sinks ?? {};
|
|
230
|
-
const braintrustSinks = createBraintrustSinks();
|
|
231
|
-
const workflowInterceptorsPath = __require.resolve(
|
|
232
|
-
"@braintrust/temporal/workflow-interceptors"
|
|
233
|
-
);
|
|
234
229
|
return {
|
|
235
230
|
...options,
|
|
236
231
|
interceptors: {
|
|
237
232
|
...options.interceptors,
|
|
238
233
|
activity: [
|
|
239
|
-
|
|
240
|
-
|
|
234
|
+
createBraintrustActivityInterceptor,
|
|
235
|
+
...options.interceptors?.activity ?? []
|
|
241
236
|
],
|
|
242
|
-
workflowModules: [
|
|
237
|
+
workflowModules: [
|
|
238
|
+
WORKFLOW_INTERCEPTORS_SPEC,
|
|
239
|
+
...options.interceptors?.workflowModules ?? []
|
|
240
|
+
]
|
|
243
241
|
},
|
|
244
242
|
sinks: {
|
|
245
|
-
...
|
|
246
|
-
...
|
|
243
|
+
...createBraintrustSinks(),
|
|
244
|
+
...options.sinks ?? {}
|
|
247
245
|
}
|
|
248
246
|
};
|
|
249
247
|
}
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -24,10 +24,12 @@
|
|
|
24
24
|
* plugins: [braintrustPlugin],
|
|
25
25
|
* });
|
|
26
26
|
*
|
|
27
|
+
* const workflowsUrl = new URL("./workflows", import.meta.url);
|
|
28
|
+
* const workflowsPath = workflowsUrl.pathname;
|
|
27
29
|
* // Create worker with the plugin
|
|
28
30
|
* const worker = await Worker.create({
|
|
29
31
|
* taskQueue: "my-queue",
|
|
30
|
-
* workflowsPath:
|
|
32
|
+
* workflowsPath: workflowsPath,
|
|
31
33
|
* activities,
|
|
32
34
|
* plugins: [braintrustPlugin],
|
|
33
35
|
* });
|
package/src/plugin.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type {
|
|
2
|
+
ClientPlugin,
|
|
3
|
+
ClientOptions,
|
|
4
|
+
WorkflowClientInterceptor,
|
|
5
|
+
WorkflowClientInterceptors,
|
|
6
|
+
} from "@temporalio/client";
|
|
2
7
|
import type { WorkerPlugin, WorkerOptions } from "@temporalio/worker";
|
|
3
8
|
import {
|
|
4
9
|
createBraintrustClientInterceptor,
|
|
@@ -6,6 +11,9 @@ import {
|
|
|
6
11
|
} from "./interceptors";
|
|
7
12
|
import { createBraintrustSinks } from "./sinks";
|
|
8
13
|
|
|
14
|
+
// Add the workflow interceptor package specifier so the Temporal bundler can include it
|
|
15
|
+
const WORKFLOW_INTERCEPTORS_SPEC = "@braintrust/temporal/workflow-interceptors";
|
|
16
|
+
|
|
9
17
|
/**
|
|
10
18
|
* A Braintrust plugin for Temporal that automatically instruments
|
|
11
19
|
* workflows and activities with tracing spans.
|
|
@@ -53,17 +61,24 @@ export class BraintrustTemporalPlugin implements ClientPlugin, WorkerPlugin {
|
|
|
53
61
|
const existing = options.interceptors?.workflow;
|
|
54
62
|
const braintrustInterceptor = createBraintrustClientInterceptor();
|
|
55
63
|
|
|
56
|
-
|
|
57
|
-
|
|
64
|
+
let workflow:
|
|
65
|
+
| WorkflowClientInterceptors
|
|
66
|
+
| WorkflowClientInterceptor[]
|
|
67
|
+
| undefined;
|
|
68
|
+
|
|
58
69
|
if (Array.isArray(existing)) {
|
|
59
|
-
workflow = [
|
|
70
|
+
workflow = [
|
|
71
|
+
...(existing as WorkflowClientInterceptor[]),
|
|
72
|
+
braintrustInterceptor,
|
|
73
|
+
];
|
|
60
74
|
} else if (existing) {
|
|
61
|
-
// It's a WorkflowClientInterceptors object, merge our interceptor
|
|
75
|
+
// It's a WorkflowClientInterceptors object, merge our interceptor into the calls array
|
|
62
76
|
workflow = {
|
|
63
77
|
...existing,
|
|
64
|
-
...braintrustInterceptor,
|
|
78
|
+
calls: [...(existing.calls ?? []), () => braintrustInterceptor],
|
|
65
79
|
};
|
|
66
80
|
} else {
|
|
81
|
+
// keep in new array form
|
|
67
82
|
workflow = [braintrustInterceptor];
|
|
68
83
|
}
|
|
69
84
|
|
|
@@ -73,40 +88,30 @@ export class BraintrustTemporalPlugin implements ClientPlugin, WorkerPlugin {
|
|
|
73
88
|
...options.interceptors,
|
|
74
89
|
workflow,
|
|
75
90
|
},
|
|
76
|
-
}
|
|
91
|
+
} as Omit<ClientOptions, "plugins">;
|
|
77
92
|
}
|
|
78
93
|
|
|
79
94
|
/**
|
|
80
95
|
* Configure the Temporal Worker with Braintrust interceptors and sinks.
|
|
81
|
-
*
|
|
82
|
-
* and the workflow interceptor modules for bundling.
|
|
96
|
+
* Prepends interceptors to ensure they run first, making plugin order irrelevant.
|
|
83
97
|
*/
|
|
84
98
|
configureWorker(options: WorkerOptions): WorkerOptions {
|
|
85
|
-
const existingActivityInterceptors = options.interceptors?.activity ?? [];
|
|
86
|
-
const existingWorkflowModules = options.interceptors?.workflowModules ?? [];
|
|
87
|
-
const existingSinks = options.sinks ?? {};
|
|
88
|
-
|
|
89
|
-
const braintrustSinks = createBraintrustSinks();
|
|
90
|
-
|
|
91
|
-
// Resolve the workflow interceptors module path
|
|
92
|
-
// This needs to be resolved at runtime to get the actual file path
|
|
93
|
-
const workflowInterceptorsPath = require.resolve(
|
|
94
|
-
"@braintrust/temporal/workflow-interceptors",
|
|
95
|
-
);
|
|
96
|
-
|
|
97
99
|
return {
|
|
98
100
|
...options,
|
|
99
101
|
interceptors: {
|
|
100
102
|
...options.interceptors,
|
|
101
103
|
activity: [
|
|
102
|
-
...existingActivityInterceptors,
|
|
103
104
|
createBraintrustActivityInterceptor,
|
|
105
|
+
...(options.interceptors?.activity ?? []),
|
|
106
|
+
],
|
|
107
|
+
workflowModules: [
|
|
108
|
+
WORKFLOW_INTERCEPTORS_SPEC,
|
|
109
|
+
...(options.interceptors?.workflowModules ?? []),
|
|
104
110
|
],
|
|
105
|
-
workflowModules: [...existingWorkflowModules, workflowInterceptorsPath],
|
|
106
111
|
},
|
|
107
112
|
sinks: {
|
|
108
|
-
...
|
|
109
|
-
...
|
|
113
|
+
...createBraintrustSinks(),
|
|
114
|
+
...(options.sinks ?? {}),
|
|
110
115
|
},
|
|
111
116
|
};
|
|
112
117
|
}
|
|
@@ -1,183 +0,0 @@
|
|
|
1
|
-
# Temporal + Braintrust Tracing Example
|
|
2
|
-
|
|
3
|
-
This example demonstrates how to integrate Braintrust tracing with Temporal workflows and activities.
|
|
4
|
-
|
|
5
|
-
## Prerequisites
|
|
6
|
-
|
|
7
|
-
- [mise](https://mise.jdx.dev/) (recommended) - automatically installs all dependencies
|
|
8
|
-
- OR manually install:
|
|
9
|
-
- Node.js 20+
|
|
10
|
-
- `pnpm`
|
|
11
|
-
- Temporal CLI (`temporal`)
|
|
12
|
-
- Optional: [`overmind`](https://github.com/DarthSim/overmind) (only if you want to use the included `Procfile`)
|
|
13
|
-
|
|
14
|
-
### Option 1: Using mise (recommended)
|
|
15
|
-
|
|
16
|
-
[mise](https://mise.jdx.dev/) will automatically install and manage all required tools (Node.js, Temporal CLI, overmind, and dependencies):
|
|
17
|
-
|
|
18
|
-
**Install mise:**
|
|
19
|
-
|
|
20
|
-
```bash
|
|
21
|
-
# macOS/Linux
|
|
22
|
-
curl https://mise.run | sh
|
|
23
|
-
|
|
24
|
-
# Or using Homebrew
|
|
25
|
-
brew install mise
|
|
26
|
-
```
|
|
27
|
-
|
|
28
|
-
**Setup and run:**
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
# Copy and configure environment
|
|
32
|
-
cp .env.example .env
|
|
33
|
-
# Edit .env with your BRAINTRUST_API_KEY
|
|
34
|
-
|
|
35
|
-
# mise will automatically install tools and dependencies
|
|
36
|
-
mise run server # Start temporal server and workers
|
|
37
|
-
|
|
38
|
-
# In another terminal:
|
|
39
|
-
mise run workflow # Run the workflow client
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
**Available mise tasks:**
|
|
43
|
-
|
|
44
|
-
```bash
|
|
45
|
-
mise run install # Install dependencies
|
|
46
|
-
mise run server # Run temporal server and workers
|
|
47
|
-
mise run workflow # Run workflow client
|
|
48
|
-
mise run stop # Stop temporal server and workers
|
|
49
|
-
mise run kill # Force kill all processes
|
|
50
|
-
```
|
|
51
|
-
|
|
52
|
-
### Option 2: Manual installation
|
|
53
|
-
|
|
54
|
-
#### Installing Temporal CLI
|
|
55
|
-
|
|
56
|
-
The Temporal CLI is required to run the local Temporal server:
|
|
57
|
-
|
|
58
|
-
**macOS:**
|
|
59
|
-
|
|
60
|
-
```bash
|
|
61
|
-
brew install temporal
|
|
62
|
-
```
|
|
63
|
-
|
|
64
|
-
**Linux:**
|
|
65
|
-
|
|
66
|
-
```bash
|
|
67
|
-
# Using Homebrew
|
|
68
|
-
brew install temporal
|
|
69
|
-
|
|
70
|
-
# Or using curl
|
|
71
|
-
curl -sSf https://temporal.download/cli.sh | sh
|
|
72
|
-
```
|
|
73
|
-
|
|
74
|
-
**Windows:**
|
|
75
|
-
|
|
76
|
-
```powershell
|
|
77
|
-
# Using Scoop
|
|
78
|
-
scoop install temporal
|
|
79
|
-
|
|
80
|
-
# Or download from releases
|
|
81
|
-
# https://github.com/temporalio/cli/releases
|
|
82
|
-
```
|
|
83
|
-
|
|
84
|
-
Verify the installation:
|
|
85
|
-
|
|
86
|
-
```bash
|
|
87
|
-
temporal --version
|
|
88
|
-
```
|
|
89
|
-
|
|
90
|
-
#### Installing overmind (optional)
|
|
91
|
-
|
|
92
|
-
Overmind is a process manager that makes it easy to run multiple services together. If you want to use `overmind start` to run everything at once, install it for your platform:
|
|
93
|
-
|
|
94
|
-
**macOS:**
|
|
95
|
-
|
|
96
|
-
```bash
|
|
97
|
-
brew install overmind
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
**Linux:**
|
|
101
|
-
|
|
102
|
-
```bash
|
|
103
|
-
brew install overmind
|
|
104
|
-
|
|
105
|
-
# Or download from releases
|
|
106
|
-
# https://github.com/DarthSim/overmind/releases
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
**Windows:**
|
|
110
|
-
Overmind is not officially supported on Windows. Use the manual approach below instead.
|
|
111
|
-
|
|
112
|
-
## Setup
|
|
113
|
-
|
|
114
|
-
```bash
|
|
115
|
-
# Copy and configure environment
|
|
116
|
-
cp .env.example .env
|
|
117
|
-
# Edit .env with your BRAINTRUST_API_KEY
|
|
118
|
-
|
|
119
|
-
# Install dependencies
|
|
120
|
-
pnpm install
|
|
121
|
-
```
|
|
122
|
-
|
|
123
|
-
## Running the Example
|
|
124
|
-
|
|
125
|
-
### Option 1: Using overmind
|
|
126
|
-
|
|
127
|
-
Start the temporal server and workers together:
|
|
128
|
-
|
|
129
|
-
```bash
|
|
130
|
-
overmind start
|
|
131
|
-
```
|
|
132
|
-
|
|
133
|
-
Then in another terminal, run the workflow:
|
|
134
|
-
|
|
135
|
-
```bash
|
|
136
|
-
pnpm run client
|
|
137
|
-
```
|
|
138
|
-
|
|
139
|
-
### Option 2: Manual
|
|
140
|
-
|
|
141
|
-
1. Start the Temporal server:
|
|
142
|
-
|
|
143
|
-
```bash
|
|
144
|
-
temporal server start-dev
|
|
145
|
-
```
|
|
146
|
-
|
|
147
|
-
2. Start the worker:
|
|
148
|
-
|
|
149
|
-
```bash
|
|
150
|
-
pnpm run worker
|
|
151
|
-
```
|
|
152
|
-
|
|
153
|
-
3. Run the client:
|
|
154
|
-
|
|
155
|
-
```bash
|
|
156
|
-
pnpm run client
|
|
157
|
-
|
|
158
|
-
# Or with a signal:
|
|
159
|
-
pnpm run client -- --signal
|
|
160
|
-
```
|
|
161
|
-
|
|
162
|
-
## What Gets Traced
|
|
163
|
-
|
|
164
|
-
- **Client span**: Wraps the workflow execution call
|
|
165
|
-
- **Workflow span**: Created via sinks when the workflow starts
|
|
166
|
-
- **Activity spans**: Created for each activity execution with parent linking
|
|
167
|
-
|
|
168
|
-
The trace hierarchy looks like:
|
|
169
|
-
|
|
170
|
-
```
|
|
171
|
-
Client span ("example.temporal.workflow")
|
|
172
|
-
└── Workflow span ("temporal.workflow.simpleWorkflow")
|
|
173
|
-
└── Activity span ("temporal.activity.addTen")
|
|
174
|
-
└── Activity span ("temporal.activity.multiplyByTwo")
|
|
175
|
-
└── Activity span ("temporal.activity.subtractFive")
|
|
176
|
-
```
|
|
177
|
-
|
|
178
|
-
## How It Works
|
|
179
|
-
|
|
180
|
-
1. **Client interceptor**: Captures the current Braintrust span context and adds it to workflow headers
|
|
181
|
-
2. **Workflow interceptor**: Extracts parent context from headers and creates a workflow span via sinks
|
|
182
|
-
3. **Sinks**: Allow the workflow isolate to call into Node.js to create spans (with `callDuringReplay: false`)
|
|
183
|
-
4. **Activity interceptor**: Creates spans for each activity, using the workflow span as parent
|
|
@@ -1,40 +0,0 @@
|
|
|
1
|
-
# Mise will automatically read and use .tool-versions files as well as this file.
|
|
2
|
-
[settings]
|
|
3
|
-
experimental=true
|
|
4
|
-
|
|
5
|
-
[env]
|
|
6
|
-
# See env.example to configure API keys.
|
|
7
|
-
_.file = ".env"
|
|
8
|
-
|
|
9
|
-
[tools]
|
|
10
|
-
node = "20"
|
|
11
|
-
temporal = "latest"
|
|
12
|
-
overmind = "latest"
|
|
13
|
-
|
|
14
|
-
[hooks]
|
|
15
|
-
postinstall = "mise run install"
|
|
16
|
-
|
|
17
|
-
[tasks.install]
|
|
18
|
-
description = "Install dependencies"
|
|
19
|
-
run = "pnpm install --ignore-workspace"
|
|
20
|
-
|
|
21
|
-
[tasks.server]
|
|
22
|
-
description = "Run temporal server and workers"
|
|
23
|
-
run = "overmind s"
|
|
24
|
-
|
|
25
|
-
[tasks.workflow]
|
|
26
|
-
description = "Run workflow client"
|
|
27
|
-
run = "pnpm exec ts-node src/client.ts"
|
|
28
|
-
|
|
29
|
-
[tasks.stop]
|
|
30
|
-
description = "Stop temporal server and workers"
|
|
31
|
-
run = "overmind quit || true"
|
|
32
|
-
|
|
33
|
-
[tasks.kill]
|
|
34
|
-
description = "Force kill temporal server and workers"
|
|
35
|
-
run = """
|
|
36
|
-
pkill -f 'examples/temporal.*ts-node' 2>/dev/null || true
|
|
37
|
-
pkill -f 'overmind.*temporal' 2>/dev/null || true
|
|
38
|
-
rm -f .overmind.sock
|
|
39
|
-
echo 'Server killed'
|
|
40
|
-
"""
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "temporal-example",
|
|
3
|
-
"version": "1.0.0",
|
|
4
|
-
"private": true,
|
|
5
|
-
"scripts": {
|
|
6
|
-
"build": "tsc",
|
|
7
|
-
"worker": "pnpm exec ts-node src/worker.ts",
|
|
8
|
-
"client": "pnpm exec ts-node src/client.ts"
|
|
9
|
-
},
|
|
10
|
-
"dependencies": {
|
|
11
|
-
"@braintrust/temporal": "^0.1.0",
|
|
12
|
-
"@temporalio/activity": "^1.11.0",
|
|
13
|
-
"@temporalio/client": "^1.11.0",
|
|
14
|
-
"@temporalio/common": "^1.11.0",
|
|
15
|
-
"@temporalio/worker": "^1.11.0",
|
|
16
|
-
"@temporalio/workflow": "^1.11.0",
|
|
17
|
-
"braintrust": "^2.0.0",
|
|
18
|
-
"uuid": "^9.0.0"
|
|
19
|
-
},
|
|
20
|
-
"devDependencies": {
|
|
21
|
-
"@types/node": "^20.0.0",
|
|
22
|
-
"@types/uuid": "^9.0.0",
|
|
23
|
-
"ts-node": "^10.9.0",
|
|
24
|
-
"typescript": "^5.0.0"
|
|
25
|
-
}
|
|
26
|
-
}
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import * as braintrust from "braintrust";
|
|
2
|
-
|
|
3
|
-
export interface TaskInput {
|
|
4
|
-
value: number;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export async function addTen(input: TaskInput): Promise<number> {
|
|
8
|
-
console.log(`Adding 10 to ${input.value}`);
|
|
9
|
-
|
|
10
|
-
// Test child span within activity
|
|
11
|
-
const result = await braintrust.traced(
|
|
12
|
-
async (span) => {
|
|
13
|
-
span.log({
|
|
14
|
-
input: { value: input.value, operation: "add", operand: 10 },
|
|
15
|
-
});
|
|
16
|
-
await new Promise((resolve) => setTimeout(resolve, 500));
|
|
17
|
-
const sum = input.value + 10;
|
|
18
|
-
span.log({ output: sum });
|
|
19
|
-
return sum;
|
|
20
|
-
},
|
|
21
|
-
{ name: "compute.addition" },
|
|
22
|
-
);
|
|
23
|
-
|
|
24
|
-
console.log(`Result: ${input.value} + 10 = ${result}`);
|
|
25
|
-
return result;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function multiplyByTwo(input: TaskInput): Promise<number> {
|
|
29
|
-
console.log(`Multiplying ${input.value} by 2`);
|
|
30
|
-
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
31
|
-
const result = input.value * 2;
|
|
32
|
-
console.log(`Result: ${input.value} * 2 = ${result}`);
|
|
33
|
-
return result;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export async function subtractFive(input: TaskInput): Promise<number> {
|
|
37
|
-
console.log(`Subtracting 5 from ${input.value}`);
|
|
38
|
-
await new Promise((resolve) => setTimeout(resolve, 300));
|
|
39
|
-
const result = input.value - 5;
|
|
40
|
-
console.log(`Result: ${input.value} - 5 = ${result}`);
|
|
41
|
-
return result;
|
|
42
|
-
}
|
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { Client, Connection } from "@temporalio/client";
|
|
2
|
-
import { v4 as uuid } from "uuid";
|
|
3
|
-
import * as braintrust from "braintrust";
|
|
4
|
-
import { BraintrustTemporalPlugin } from "@braintrust/temporal";
|
|
5
|
-
import { simpleWorkflow } from "./workflows";
|
|
6
|
-
import type { TaskInput } from "./activities";
|
|
7
|
-
|
|
8
|
-
const TASK_QUEUE = "braintrust-example-task-queue";
|
|
9
|
-
|
|
10
|
-
async function main() {
|
|
11
|
-
braintrust.initLogger({ projectName: "temporal-example" });
|
|
12
|
-
|
|
13
|
-
const connection = await Connection.connect({
|
|
14
|
-
address: "localhost:7233",
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
const client = new Client({
|
|
18
|
-
connection,
|
|
19
|
-
namespace: "default",
|
|
20
|
-
plugins: [new BraintrustTemporalPlugin()],
|
|
21
|
-
});
|
|
22
|
-
|
|
23
|
-
const inputData: TaskInput = { value: 5 };
|
|
24
|
-
const workflowId = `simple-workflow-${uuid().slice(0, 8)}`;
|
|
25
|
-
|
|
26
|
-
console.log(`Starting workflow with value: ${inputData.value}`);
|
|
27
|
-
console.log(`Workflow ID: ${workflowId}`);
|
|
28
|
-
|
|
29
|
-
// Wrap in a Braintrust span
|
|
30
|
-
await braintrust.traced(
|
|
31
|
-
async (span) => {
|
|
32
|
-
const handle = await client.workflow.start(simpleWorkflow, {
|
|
33
|
-
args: [inputData],
|
|
34
|
-
taskQueue: TASK_QUEUE,
|
|
35
|
-
workflowId,
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const result = await handle.result();
|
|
39
|
-
span.log({ output: result });
|
|
40
|
-
console.log(`\nResult: ${result}`);
|
|
41
|
-
console.log(`\nView trace: ${span.link()}`);
|
|
42
|
-
return result;
|
|
43
|
-
},
|
|
44
|
-
{ name: "temporal.client.simpleWorkflow" },
|
|
45
|
-
);
|
|
46
|
-
|
|
47
|
-
await braintrust.flush();
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
main().catch((err) => {
|
|
51
|
-
console.error(err);
|
|
52
|
-
process.exit(1);
|
|
53
|
-
});
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import { Worker, NativeConnection } from "@temporalio/worker";
|
|
2
|
-
import * as braintrust from "braintrust";
|
|
3
|
-
import { BraintrustTemporalPlugin } from "@braintrust/temporal";
|
|
4
|
-
import * as activities from "./activities";
|
|
5
|
-
|
|
6
|
-
const TASK_QUEUE = "braintrust-example-task-queue";
|
|
7
|
-
|
|
8
|
-
async function main() {
|
|
9
|
-
braintrust.initLogger({ projectName: "temporal-example" });
|
|
10
|
-
|
|
11
|
-
const connection = await NativeConnection.connect({
|
|
12
|
-
address: "localhost:7233",
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
const worker = await Worker.create({
|
|
16
|
-
connection,
|
|
17
|
-
namespace: "default",
|
|
18
|
-
taskQueue: TASK_QUEUE,
|
|
19
|
-
workflowsPath: require.resolve("./workflows"),
|
|
20
|
-
activities,
|
|
21
|
-
plugins: [new BraintrustTemporalPlugin()],
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
console.log(`Worker started on task queue: ${TASK_QUEUE}`);
|
|
25
|
-
await worker.run();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
main().catch((err) => {
|
|
29
|
-
console.error(err);
|
|
30
|
-
process.exit(1);
|
|
31
|
-
});
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
proxyActivities,
|
|
3
|
-
sleep,
|
|
4
|
-
workflowInfo,
|
|
5
|
-
defineSignal,
|
|
6
|
-
setHandler,
|
|
7
|
-
log,
|
|
8
|
-
} from "@temporalio/workflow";
|
|
9
|
-
import type * as activities from "./activities";
|
|
10
|
-
|
|
11
|
-
const { addTen, multiplyByTwo, subtractFive } = proxyActivities<
|
|
12
|
-
typeof activities
|
|
13
|
-
>({
|
|
14
|
-
startToCloseTimeout: "10 seconds",
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
export const addSignalValue = defineSignal<[number]>("addSignalValue");
|
|
18
|
-
|
|
19
|
-
export interface TaskInput {
|
|
20
|
-
value: number;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
export async function simpleWorkflow(input: TaskInput): Promise<string> {
|
|
24
|
-
log.info(`Starting workflow with value: ${input.value}`);
|
|
25
|
-
|
|
26
|
-
let signalValue = 0;
|
|
27
|
-
setHandler(addSignalValue, (value: number) => {
|
|
28
|
-
log.info(`Received signal with value: ${value}`);
|
|
29
|
-
signalValue += value;
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
// Step 1: Add 10
|
|
33
|
-
const step1 = await addTen({ value: input.value });
|
|
34
|
-
log.info(`After step 1: ${step1}`);
|
|
35
|
-
|
|
36
|
-
// Step 2: Multiply by 2
|
|
37
|
-
const step2 = await multiplyByTwo({ value: step1 });
|
|
38
|
-
log.info(`After step 2: ${step2}`);
|
|
39
|
-
|
|
40
|
-
// Step 3: Subtract 5
|
|
41
|
-
const step3 = await subtractFive({ value: step2 });
|
|
42
|
-
log.info(`After step 3: ${step3}`);
|
|
43
|
-
|
|
44
|
-
const finalResult = `Complete: ${input.value} -> +10=${step1} -> *2=${step2} -> -5=${step3} + signal(${signalValue}) = ${step3 + signalValue}`;
|
|
45
|
-
log.info(finalResult);
|
|
46
|
-
return finalResult;
|
|
47
|
-
}
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"compilerOptions": {
|
|
3
|
-
"target": "ES2021",
|
|
4
|
-
"module": "commonjs",
|
|
5
|
-
"lib": ["ES2021"],
|
|
6
|
-
"strict": true,
|
|
7
|
-
"esModuleInterop": true,
|
|
8
|
-
"skipLibCheck": true,
|
|
9
|
-
"forceConsistentCasingInFileNames": true,
|
|
10
|
-
"outDir": "./dist",
|
|
11
|
-
"rootDir": "./src",
|
|
12
|
-
"declaration": true
|
|
13
|
-
},
|
|
14
|
-
"include": ["src/**/*"]
|
|
15
|
-
}
|
package/src/temporal.test.ts
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
import { expect, test, describe } from "vitest";
|
|
2
|
-
import {
|
|
3
|
-
serializeHeaderValue,
|
|
4
|
-
deserializeHeaderValue,
|
|
5
|
-
BRAINTRUST_SPAN_HEADER,
|
|
6
|
-
BRAINTRUST_WORKFLOW_SPAN_HEADER,
|
|
7
|
-
BRAINTRUST_WORKFLOW_SPAN_ID_HEADER,
|
|
8
|
-
} from "./utils";
|
|
9
|
-
import { SpanComponentsV3, SpanObjectTypeV3 } from "braintrust/util";
|
|
10
|
-
import {
|
|
11
|
-
BraintrustTemporalPlugin,
|
|
12
|
-
createBraintrustTemporalPlugin,
|
|
13
|
-
} from "./plugin";
|
|
14
|
-
|
|
15
|
-
describe("temporal header utilities", () => {
|
|
16
|
-
test("serializeHeaderValue encodes string correctly", () => {
|
|
17
|
-
const value = "test-span-id";
|
|
18
|
-
const payload = serializeHeaderValue(value);
|
|
19
|
-
|
|
20
|
-
expect(payload.metadata?.encoding).toBeDefined();
|
|
21
|
-
expect(payload.data).toBeDefined();
|
|
22
|
-
expect(new TextDecoder().decode(payload.metadata?.encoding)).toBe(
|
|
23
|
-
"json/plain",
|
|
24
|
-
);
|
|
25
|
-
expect(new TextDecoder().decode(payload.data)).toBe('"test-span-id"');
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
test("deserializeHeaderValue decodes payload correctly", () => {
|
|
29
|
-
const original = "test-value-123";
|
|
30
|
-
const payload = serializeHeaderValue(original);
|
|
31
|
-
const decoded = deserializeHeaderValue(payload);
|
|
32
|
-
|
|
33
|
-
expect(decoded).toBe(original);
|
|
34
|
-
});
|
|
35
|
-
|
|
36
|
-
test("deserializeHeaderValue handles undefined payload", () => {
|
|
37
|
-
expect(deserializeHeaderValue(undefined)).toBeUndefined();
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
test("deserializeHeaderValue handles payload without data", () => {
|
|
41
|
-
expect(deserializeHeaderValue({ metadata: {} })).toBeUndefined();
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
test("deserializeHeaderValue handles invalid JSON", () => {
|
|
45
|
-
const payload = {
|
|
46
|
-
data: new TextEncoder().encode("not valid json"),
|
|
47
|
-
};
|
|
48
|
-
expect(deserializeHeaderValue(payload)).toBeUndefined();
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
test("round-trip serialization preserves complex strings", () => {
|
|
52
|
-
const testCases = [
|
|
53
|
-
"simple",
|
|
54
|
-
"with spaces",
|
|
55
|
-
"with/slashes",
|
|
56
|
-
"unicode-日本語",
|
|
57
|
-
"emoji-👋",
|
|
58
|
-
"",
|
|
59
|
-
];
|
|
60
|
-
|
|
61
|
-
for (const value of testCases) {
|
|
62
|
-
const payload = serializeHeaderValue(value);
|
|
63
|
-
const decoded = deserializeHeaderValue(payload);
|
|
64
|
-
expect(decoded).toBe(value);
|
|
65
|
-
}
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
test("header constants are defined", () => {
|
|
69
|
-
expect(BRAINTRUST_SPAN_HEADER).toBe("_braintrust-span");
|
|
70
|
-
expect(BRAINTRUST_WORKFLOW_SPAN_HEADER).toBe("_braintrust-workflow-span");
|
|
71
|
-
expect(BRAINTRUST_WORKFLOW_SPAN_ID_HEADER).toBe(
|
|
72
|
-
"_braintrust-workflow-span-id",
|
|
73
|
-
);
|
|
74
|
-
});
|
|
75
|
-
});
|
|
76
|
-
|
|
77
|
-
describe("SpanComponentsV3 cross-worker reconstruction", () => {
|
|
78
|
-
test("can parse and reconstruct span components with new span_id", () => {
|
|
79
|
-
const clientComponents = new SpanComponentsV3({
|
|
80
|
-
object_type: SpanObjectTypeV3.PROJECT_LOGS,
|
|
81
|
-
object_id: "project-123",
|
|
82
|
-
row_id: "row-456",
|
|
83
|
-
span_id: "client-span-id",
|
|
84
|
-
root_span_id: "root-span-id",
|
|
85
|
-
});
|
|
86
|
-
|
|
87
|
-
const clientContext = clientComponents.toStr();
|
|
88
|
-
const workflowSpanId = "workflow-span-id";
|
|
89
|
-
|
|
90
|
-
const parsed = SpanComponentsV3.fromStr(clientContext);
|
|
91
|
-
const data = parsed.data;
|
|
92
|
-
|
|
93
|
-
expect(data.row_id).toBe("row-456");
|
|
94
|
-
expect(data.root_span_id).toBe("root-span-id");
|
|
95
|
-
expect(data.span_id).toBe("client-span-id");
|
|
96
|
-
|
|
97
|
-
if (data.row_id && data.root_span_id) {
|
|
98
|
-
const workflowComponents = new SpanComponentsV3({
|
|
99
|
-
object_type: data.object_type,
|
|
100
|
-
object_id: data.object_id,
|
|
101
|
-
propagated_event: data.propagated_event,
|
|
102
|
-
row_id: data.row_id,
|
|
103
|
-
root_span_id: data.root_span_id,
|
|
104
|
-
span_id: workflowSpanId,
|
|
105
|
-
});
|
|
106
|
-
|
|
107
|
-
const reconstructed = SpanComponentsV3.fromStr(
|
|
108
|
-
workflowComponents.toStr(),
|
|
109
|
-
);
|
|
110
|
-
expect(reconstructed.data.span_id).toBe(workflowSpanId);
|
|
111
|
-
expect(reconstructed.data.row_id).toBe("row-456");
|
|
112
|
-
expect(reconstructed.data.root_span_id).toBe("root-span-id");
|
|
113
|
-
expect(reconstructed.data.object_id).toBe("project-123");
|
|
114
|
-
}
|
|
115
|
-
});
|
|
116
|
-
|
|
117
|
-
test("preserves object_type when reconstructing", () => {
|
|
118
|
-
const objectTypes = [
|
|
119
|
-
SpanObjectTypeV3.PROJECT_LOGS,
|
|
120
|
-
SpanObjectTypeV3.EXPERIMENT,
|
|
121
|
-
SpanObjectTypeV3.PLAYGROUND_LOGS,
|
|
122
|
-
];
|
|
123
|
-
|
|
124
|
-
for (const objectType of objectTypes) {
|
|
125
|
-
const original = new SpanComponentsV3({
|
|
126
|
-
object_type: objectType,
|
|
127
|
-
object_id: "test-id",
|
|
128
|
-
row_id: "row-id",
|
|
129
|
-
span_id: "original-span-id",
|
|
130
|
-
root_span_id: "root-span-id",
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
const parsed = SpanComponentsV3.fromStr(original.toStr());
|
|
134
|
-
|
|
135
|
-
const reconstructed = new SpanComponentsV3({
|
|
136
|
-
object_type: parsed.data.object_type,
|
|
137
|
-
object_id: parsed.data.object_id,
|
|
138
|
-
propagated_event: parsed.data.propagated_event,
|
|
139
|
-
row_id: parsed.data.row_id!,
|
|
140
|
-
root_span_id: parsed.data.root_span_id!,
|
|
141
|
-
span_id: "new-span-id",
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
expect(reconstructed.data.object_type).toBe(objectType);
|
|
145
|
-
}
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test("handles span components without row_id fields", () => {
|
|
149
|
-
const componentsWithoutRowId = new SpanComponentsV3({
|
|
150
|
-
object_type: SpanObjectTypeV3.PROJECT_LOGS,
|
|
151
|
-
object_id: "test-id",
|
|
152
|
-
});
|
|
153
|
-
|
|
154
|
-
const parsed = SpanComponentsV3.fromStr(componentsWithoutRowId.toStr());
|
|
155
|
-
|
|
156
|
-
expect(parsed.data.row_id).toBeUndefined();
|
|
157
|
-
expect(parsed.data.span_id).toBeUndefined();
|
|
158
|
-
expect(parsed.data.root_span_id).toBeUndefined();
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
test("preserves propagated_event when reconstructing", () => {
|
|
162
|
-
const propagatedEvent = { key: "value", nested: { inner: 123 } };
|
|
163
|
-
|
|
164
|
-
const original = new SpanComponentsV3({
|
|
165
|
-
object_type: SpanObjectTypeV3.PROJECT_LOGS,
|
|
166
|
-
object_id: "test-id",
|
|
167
|
-
propagated_event: propagatedEvent,
|
|
168
|
-
row_id: "row-id",
|
|
169
|
-
span_id: "original-span-id",
|
|
170
|
-
root_span_id: "root-span-id",
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
const parsed = SpanComponentsV3.fromStr(original.toStr());
|
|
174
|
-
|
|
175
|
-
const reconstructed = new SpanComponentsV3({
|
|
176
|
-
object_type: parsed.data.object_type,
|
|
177
|
-
object_id: parsed.data.object_id,
|
|
178
|
-
propagated_event: parsed.data.propagated_event,
|
|
179
|
-
row_id: parsed.data.row_id!,
|
|
180
|
-
root_span_id: parsed.data.root_span_id!,
|
|
181
|
-
span_id: "new-span-id",
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
expect(reconstructed.data.propagated_event).toEqual(propagatedEvent);
|
|
185
|
-
});
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
describe("BraintrustTemporalPlugin", () => {
|
|
189
|
-
test("createBraintrustTemporalPlugin returns a plugin instance", () => {
|
|
190
|
-
const plugin = createBraintrustTemporalPlugin();
|
|
191
|
-
expect(plugin).toBeInstanceOf(BraintrustTemporalPlugin);
|
|
192
|
-
expect(plugin.name).toBe("braintrust");
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
test("plugin has configureClient method", () => {
|
|
196
|
-
const plugin = createBraintrustTemporalPlugin();
|
|
197
|
-
expect(typeof plugin.configureClient).toBe("function");
|
|
198
|
-
});
|
|
199
|
-
|
|
200
|
-
test("plugin has configureWorker method", () => {
|
|
201
|
-
const plugin = createBraintrustTemporalPlugin();
|
|
202
|
-
expect(typeof plugin.configureWorker).toBe("function");
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
test("configureClient adds workflow interceptor", () => {
|
|
206
|
-
const plugin = createBraintrustTemporalPlugin();
|
|
207
|
-
const options = {};
|
|
208
|
-
const configured = plugin.configureClient(options);
|
|
209
|
-
|
|
210
|
-
expect(configured.interceptors).toBeDefined();
|
|
211
|
-
expect(configured.interceptors?.workflow).toBeDefined();
|
|
212
|
-
expect(Array.isArray(configured.interceptors?.workflow)).toBe(true);
|
|
213
|
-
expect(configured.interceptors?.workflow?.length).toBe(1);
|
|
214
|
-
});
|
|
215
|
-
|
|
216
|
-
test("configureClient preserves existing interceptors", () => {
|
|
217
|
-
const plugin = createBraintrustTemporalPlugin();
|
|
218
|
-
const existingInterceptor = { start: async (i: unknown, n: unknown) => n };
|
|
219
|
-
const options = {
|
|
220
|
-
interceptors: {
|
|
221
|
-
workflow: [existingInterceptor],
|
|
222
|
-
},
|
|
223
|
-
};
|
|
224
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
225
|
-
const configured = plugin.configureClient(options as any);
|
|
226
|
-
|
|
227
|
-
expect(configured.interceptors?.workflow?.length).toBe(2);
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
test("configureWorker adds activity interceptor and sinks", () => {
|
|
231
|
-
const plugin = createBraintrustTemporalPlugin();
|
|
232
|
-
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
233
|
-
const options = {} as any;
|
|
234
|
-
const configured = plugin.configureWorker(options);
|
|
235
|
-
|
|
236
|
-
expect(configured.interceptors).toBeDefined();
|
|
237
|
-
expect(configured.interceptors?.activity).toBeDefined();
|
|
238
|
-
expect(Array.isArray(configured.interceptors?.activity)).toBe(true);
|
|
239
|
-
expect(configured.interceptors?.activity?.length).toBe(1);
|
|
240
|
-
expect(configured.sinks).toBeDefined();
|
|
241
|
-
expect(configured.sinks?.braintrust).toBeDefined();
|
|
242
|
-
});
|
|
243
|
-
});
|