brass-runtime 1.15.0 → 1.16.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 (209) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +673 -136
  3. package/dist/agent/cli/main.cjs +40 -35
  4. package/dist/agent/cli/main.js +9 -4
  5. package/dist/agent/cli/main.mjs +9 -4
  6. package/dist/agent/index.cjs +8 -4
  7. package/dist/agent/index.d.ts +1 -1
  8. package/dist/agent/index.js +7 -3
  9. package/dist/agent/index.mjs +7 -3
  10. package/dist/chunk-2HQTDLHF.mjs +683 -0
  11. package/dist/chunk-36I3M4UC.mjs +370 -0
  12. package/dist/chunk-3AYM6WPJ.js +1629 -0
  13. package/dist/chunk-3LOYJFRR.cjs +300 -0
  14. package/dist/chunk-3RG5ZIWI.js +10 -0
  15. package/dist/chunk-3Y2RIUMM.js +300 -0
  16. package/dist/{chunk-VEZNF5GZ.cjs → chunk-4ROBZFL6.cjs} +130 -126
  17. package/dist/{chunk-3QMOKAS5.js → chunk-52OB2ROS.js} +9 -5
  18. package/dist/chunk-52PPNNI4.cjs +416 -0
  19. package/dist/chunk-5EC274J5.cjs +2874 -0
  20. package/dist/chunk-5QC7LRZ3.js +229 -0
  21. package/dist/chunk-5VRJNBLZ.mjs +2874 -0
  22. package/dist/chunk-62AZW6UT.cjs +313 -0
  23. package/dist/chunk-6IXXWIUM.js +683 -0
  24. package/dist/chunk-74ZTY6CP.js +2871 -0
  25. package/dist/chunk-76YMRMH2.cjs +777 -0
  26. package/dist/chunk-7CMJS3QE.mjs +2871 -0
  27. package/dist/{chunk-4NHES7VK.mjs → chunk-7JIJOVCT.js} +27 -13
  28. package/dist/chunk-A2OM6NEH.mjs +194 -0
  29. package/dist/chunk-AGR5B2BC.cjs +683 -0
  30. package/dist/chunk-AVNQLJ5V.js +777 -0
  31. package/dist/chunk-B33ICAKP.js +313 -0
  32. package/dist/{chunk-ELOOF35R.mjs → chunk-B5JD23U7.mjs} +1 -1
  33. package/dist/chunk-BABBZK4Y.js +2024 -0
  34. package/dist/chunk-C3MDXTRZ.js +354 -0
  35. package/dist/chunk-CIZFIMK5.js +2193 -0
  36. package/dist/chunk-CZIVE6NT.cjs +354 -0
  37. package/dist/chunk-DNFJLJMW.mjs +354 -0
  38. package/dist/chunk-DNFO2EIZ.mjs +777 -0
  39. package/dist/chunk-EJ6BPYVR.mjs +416 -0
  40. package/dist/chunk-ENKODRU3.cjs +2193 -0
  41. package/dist/chunk-EOC4UHBS.mjs +229 -0
  42. package/dist/{chunk-BMH5AV44.js → chunk-FH2X7BVP.js} +756 -440
  43. package/dist/{chunk-PPUXIH5R.js → chunk-FHQGHPMO.mjs} +27 -13
  44. package/dist/{chunk-TGIFUAK4.cjs → chunk-GLE2WY7Z.cjs} +951 -635
  45. package/dist/{chunk-BDF4AMWX.mjs → chunk-GYM3LLGS.mjs} +756 -440
  46. package/dist/chunk-HLWLMW2F.mjs +2024 -0
  47. package/dist/chunk-JF5WGYJJ.cjs +194 -0
  48. package/dist/chunk-KH4SYAOS.mjs +1629 -0
  49. package/dist/chunk-KN32XNTH.mjs +313 -0
  50. package/dist/chunk-KQLYONSE.cjs +2871 -0
  51. package/dist/{chunk-STVLQ3XD.cjs → chunk-KZJQ723N.cjs} +92 -78
  52. package/dist/chunk-L2SYFEBS.js +194 -0
  53. package/dist/chunk-L6VB5N7Q.cjs +104 -0
  54. package/dist/{chunk-K6M7MDZ4.mjs → chunk-MBEJI5HF.mjs} +9 -5
  55. package/dist/chunk-MIIYDLGM.js +2874 -0
  56. package/dist/chunk-MOO4L7F4.mjs +104 -0
  57. package/dist/chunk-MT3OWDPC.mjs +2193 -0
  58. package/dist/chunk-MVGUEJ5Z.cjs +370 -0
  59. package/dist/chunk-OBGZSXTJ.cjs +10 -0
  60. package/dist/chunk-PD4EJTQC.cjs +229 -0
  61. package/dist/chunk-PWC3RBQE.mjs +300 -0
  62. package/dist/chunk-Q2I37RP3.cjs +1629 -0
  63. package/dist/chunk-RKGKFN2A.js +416 -0
  64. package/dist/{chunk-R3R2FVLG.cjs → chunk-SA6HUJVI.cjs} +5 -5
  65. package/dist/chunk-TRM4JUZQ.js +104 -0
  66. package/dist/chunk-UB4B6OFY.js +370 -0
  67. package/dist/{chunk-TO7IKXYT.js → chunk-UCUBNWM2.js} +1 -1
  68. package/dist/chunk-VN44DYYT.cjs +2024 -0
  69. package/dist/chunk-Y6FXYEAI.mjs +10 -0
  70. package/dist/client-CZHU674n.d.ts +820 -0
  71. package/dist/core/index.cjs +198 -4
  72. package/dist/core/index.d.ts +311 -212
  73. package/dist/core/index.js +237 -43
  74. package/dist/core/index.mjs +237 -43
  75. package/dist/{effect-CMOQKX8y.d.ts → effect-DIUHZ9IN.d.ts} +195 -1
  76. package/dist/effectRunner-CFLC32IK.cjs +8 -0
  77. package/dist/effectRunner-L4S7IPT3.js +8 -0
  78. package/dist/effectRunner-NNGG75QA.mjs +8 -0
  79. package/dist/http/index.cjs +1227 -2971
  80. package/dist/http/index.d.ts +826 -280
  81. package/dist/http/index.js +1089 -2833
  82. package/dist/http/index.mjs +1089 -2833
  83. package/dist/http/testing.cjs +161 -0
  84. package/dist/http/testing.d.ts +43 -0
  85. package/dist/http/testing.js +161 -0
  86. package/dist/http/testing.mjs +161 -0
  87. package/dist/index.cjs +486 -250
  88. package/dist/index.d.ts +87 -95
  89. package/dist/index.js +391 -155
  90. package/dist/index.mjs +391 -155
  91. package/dist/observability/index.cjs +162 -0
  92. package/dist/observability/index.d.ts +152 -0
  93. package/dist/observability/index.js +162 -0
  94. package/dist/observability/index.mjs +162 -0
  95. package/dist/perf/cli.cjs +401 -0
  96. package/dist/perf/cli.d.ts +1 -0
  97. package/dist/perf/cli.js +401 -0
  98. package/dist/perf/cli.mjs +401 -0
  99. package/dist/perf/index.cjs +141 -0
  100. package/dist/perf/index.d.ts +483 -0
  101. package/dist/perf/index.js +141 -0
  102. package/dist/perf/index.mjs +141 -0
  103. package/dist/schedule-CK3Ml_7p.d.ts +259 -0
  104. package/dist/schema/index.cjs +29 -0
  105. package/dist/schema/index.d.ts +179 -0
  106. package/dist/schema/index.js +29 -0
  107. package/dist/schema/index.mjs +29 -0
  108. package/dist/server-GJPg8ZSG.d.ts +675 -0
  109. package/dist/{stream-FQm9h4Mg.d.ts → stream-B4oK9JFP.d.ts} +1 -1
  110. package/dist/tracer-Hwt1cl7h.d.ts +189 -0
  111. package/dist/tracing-DqbTKGcf.d.ts +148 -0
  112. package/docs/ARCHITECTURE.md +292 -0
  113. package/docs/README.md +63 -0
  114. package/docs/adr/0001-ai-context-pack.md +32 -0
  115. package/docs/agent-apply-mode.md +104 -0
  116. package/docs/agent-approvals.md +110 -0
  117. package/docs/agent-batch.md +185 -0
  118. package/docs/agent-boundaries.md +112 -0
  119. package/docs/agent-chat-sessions.md +160 -0
  120. package/docs/agent-ci.md +17 -0
  121. package/docs/agent-cli.md +405 -0
  122. package/docs/agent-config.md +480 -0
  123. package/docs/agent-context-discovery.md +159 -0
  124. package/docs/agent-copilot-like-dx.md +126 -0
  125. package/docs/agent-declarative-optimized-planning.md +138 -0
  126. package/docs/agent-dx.md +224 -0
  127. package/docs/agent-env-files.md +126 -0
  128. package/docs/agent-follow-up-context.md +43 -0
  129. package/docs/agent-global-usage.md +180 -0
  130. package/docs/agent-init.md +109 -0
  131. package/docs/agent-install-and-configure.md +516 -0
  132. package/docs/agent-language-workspace-ux.md +99 -0
  133. package/docs/agent-llm-adapters.md +123 -0
  134. package/docs/agent-local-install.md +190 -0
  135. package/docs/agent-local-tests.md +51 -0
  136. package/docs/agent-observability.md +155 -0
  137. package/docs/agent-patch-quality-loop.md +162 -0
  138. package/docs/agent-presets.md +22 -0
  139. package/docs/agent-project-commands.md +237 -0
  140. package/docs/agent-project-intelligence.md +156 -0
  141. package/docs/agent-redaction.md +18 -0
  142. package/docs/agent-release-readiness.md +76 -0
  143. package/docs/agent-rollback-safety.md +162 -0
  144. package/docs/agent-rollback.md +23 -0
  145. package/docs/agent-run-artifacts.md +16 -0
  146. package/docs/agent-vscode-auto-discovery.md +137 -0
  147. package/docs/agent-vscode-batch-runner.md +100 -0
  148. package/docs/agent-vscode-chat-layout.md +90 -0
  149. package/docs/agent-vscode-clean-install.md +147 -0
  150. package/docs/agent-vscode-code-actions.md +70 -0
  151. package/docs/agent-vscode-diff-preview.md +45 -0
  152. package/docs/agent-vscode-inline-assist.md +56 -0
  153. package/docs/agent-vscode-install.md +186 -0
  154. package/docs/agent-vscode-model-setup.md +97 -0
  155. package/docs/agent-vscode-patch-preview.md +92 -0
  156. package/docs/agent-vscode-problems.md +79 -0
  157. package/docs/agent-vscode-project-dashboard.md +106 -0
  158. package/docs/agent-vscode-run-history.md +92 -0
  159. package/docs/agent-vscode-ux.md +73 -0
  160. package/docs/ai/INVARIANTS.md +84 -0
  161. package/docs/ai/PROJECT_MAP.md +338 -0
  162. package/docs/ai/PUBLIC_API.md +336 -0
  163. package/docs/ai/VALIDATION_MATRIX.md +67 -0
  164. package/docs/api-polish.md +37 -0
  165. package/docs/cancellation.md +162 -0
  166. package/docs/coverage.md +46 -0
  167. package/docs/getting-started.md +159 -0
  168. package/docs/guides/README.md +40 -0
  169. package/docs/guides/circuit-breaker.md +89 -0
  170. package/docs/guides/error-handling.md +91 -0
  171. package/docs/guides/getting-started.md +107 -0
  172. package/docs/guides/layers.md +189 -0
  173. package/docs/guides/metrics.md +101 -0
  174. package/docs/guides/resource-management.md +141 -0
  175. package/docs/guides/retry.md +215 -0
  176. package/docs/guides/semaphore.md +66 -0
  177. package/docs/guides/streams.md +117 -0
  178. package/docs/guides/supervisors.md +98 -0
  179. package/docs/guides/testing.md +162 -0
  180. package/docs/guides/tracing.md +71 -0
  181. package/docs/http-recipes.md +399 -0
  182. package/docs/http.md +749 -0
  183. package/docs/modules.md +285 -0
  184. package/docs/observability-collector-smoke.md +31 -0
  185. package/docs/observability-framework-examples.md +98 -0
  186. package/docs/observability.md +542 -0
  187. package/docs/otel-collector-smoke.yaml +27 -0
  188. package/docs/performance-profiler.md +199 -0
  189. package/docs/production-readiness.md +73 -0
  190. package/docs/recipes/README.md +12 -0
  191. package/docs/recipes/http-server.md +45 -0
  192. package/docs/recipes/layers.md +44 -0
  193. package/docs/recipes/performance.md +47 -0
  194. package/docs/recipes/runtime.md +41 -0
  195. package/docs/recipes/testing.md +41 -0
  196. package/docs/release.md +53 -0
  197. package/docs/wasm-bounded-queues.md +44 -0
  198. package/docs/wasm-engine-observability-benchmarks.md +85 -0
  199. package/docs/wasm-fiber-engine.md +117 -0
  200. package/docs/wasm-scheduler-state-machine.md +122 -0
  201. package/docs/wasm-stream-chunks.md +54 -0
  202. package/package.json +48 -2
  203. package/dist/chunk-AR22SXML.js +0 -1043
  204. package/dist/chunk-BDYEENHT.js +0 -224
  205. package/dist/chunk-JFPU5GQI.mjs +0 -1043
  206. package/dist/chunk-MS34J5LY.cjs +0 -224
  207. package/dist/chunk-UMAZLXAB.mjs +0 -224
  208. package/dist/chunk-XPZNXSVN.cjs +0 -1043
  209. package/dist/tracing-DNT9jEbr.d.ts +0 -106
@@ -0,0 +1,98 @@
1
+ # Supervisors
2
+
3
+ Supervisors let a runtime own groups of child fibers with explicit restart and
4
+ escalation policy. Use them for long-lived workers, polling loops, connection
5
+ refreshers, and other background work that should fail in a predictable shape.
6
+
7
+ ```ts
8
+ import { Runtime, fixed, joinSupervised, makeSupervisor } from "brass-runtime";
9
+
10
+ const runtime = Runtime.make({});
11
+ const supervisor = makeSupervisor(runtime, {
12
+ strategy: "one-for-one",
13
+ restart: {
14
+ mode: "on-failure",
15
+ maxRestarts: 5,
16
+ withinMs: 60_000,
17
+ schedule: fixed(250),
18
+ },
19
+ });
20
+
21
+ const worker = supervisor.start({
22
+ name: "token-refresh",
23
+ effect: () => refreshTokenLoop(),
24
+ });
25
+
26
+ await runtime.toPromise(joinSupervised(worker));
27
+ ```
28
+
29
+ ## Strategies
30
+
31
+ - `one-for-one`: restart only the child that failed.
32
+ - `all-for-one`: interrupt and restart siblings when one child fails.
33
+
34
+ ## Restart Policy
35
+
36
+ `restart` can be `"never"`, `"always"`, `"on-failure"`, or an object:
37
+
38
+ ```ts
39
+ const supervisor = makeSupervisor(runtime, {
40
+ restart: {
41
+ mode: "on-failure",
42
+ maxRestarts: 10,
43
+ withinMs: 30_000,
44
+ delayMs: ({ restartCount }) => Math.min(1_000, restartCount * 100),
45
+ },
46
+ });
47
+ ```
48
+
49
+ For reusable timing behavior, pass a `Schedule`:
50
+
51
+ ```ts
52
+ import { exponential, jitter, makeSupervisor } from "brass-runtime";
53
+
54
+ const supervisor = makeSupervisor(runtime, {
55
+ restart: {
56
+ mode: "on-failure",
57
+ schedule: jitter(exponential(100, 5_000), { factor: 0.2 }),
58
+ },
59
+ });
60
+ ```
61
+
62
+ ## Escalation
63
+
64
+ When the restart budget is exhausted, escalation controls what happens next:
65
+
66
+ - `shutdown` interrupts sibling fibers.
67
+ - `ignore` completes only the failed child and leaves siblings running.
68
+
69
+ ```ts
70
+ const supervisor = makeSupervisor(runtime, {
71
+ strategy: "all-for-one",
72
+ restart: { mode: "on-failure", maxRestarts: 3 },
73
+ escalation: "shutdown",
74
+ });
75
+ ```
76
+
77
+ ## Observability
78
+
79
+ Supervisors emit runtime events through the existing `RuntimeHooks` pipeline:
80
+
81
+ - `supervisor.child.start`
82
+ - `supervisor.child.end`
83
+ - `supervisor.child.restart`
84
+ - `supervisor.child.escalate`
85
+ - `supervisor.shutdown`
86
+
87
+ Attach an `EventBus`, metrics sink, structured logger, or observability preset
88
+ the same way you do for fibers/scopes.
89
+
90
+ ## Shutdown
91
+
92
+ Call `shutdown()` during graceful process termination. It cancels pending
93
+ restart timers, interrupts running children, and completes when current child
94
+ fibers have observed interruption.
95
+
96
+ ```ts
97
+ await runtime.toPromise(supervisor.shutdown());
98
+ ```
@@ -0,0 +1,162 @@
1
+ # Testing Utilities
2
+
3
+ brass-runtime provides helpers for testing effects deterministically.
4
+
5
+ ## TestRuntime
6
+
7
+ ```ts
8
+ import { makeTestRuntime } from "brass-runtime";
9
+
10
+ const { runtime, run, runExit, clock, advance, flushAll } = makeTestRuntime();
11
+
12
+ // run() returns the value (throws on failure)
13
+ const value = await run(myEffect);
14
+
15
+ // runExit() returns the full Exit (never throws)
16
+ const exit = await runExit(myEffect);
17
+ if (exit._tag === "Success") console.log(exit.value);
18
+ else console.log(exit.cause);
19
+ ```
20
+
21
+ `makeTestRuntime()` uses the TypeScript runtime engine with:
22
+
23
+ - `TestScheduler`, a deterministic scheduler you can inspect and flush.
24
+ - `TestClock`, a virtual clock used by `sleep`, `timeout`, retry backoff,
25
+ `delayedEffect`, and `Runtime.delay`.
26
+ - The same fiber interpreter as production TS mode.
27
+
28
+ The scheduler auto-flushes by default for ergonomic tests. Disable that when
29
+ you need to inspect queued work:
30
+
31
+ ```ts
32
+ import { makeTestRuntime, succeed } from "brass-runtime";
33
+
34
+ const { runtime, scheduler, flushAll } = makeTestRuntime({}, { autoFlush: false });
35
+
36
+ const pending = runtime.toPromise(succeed("ok"));
37
+ expect(scheduler.size()).toBe(1);
38
+
39
+ flushAll();
40
+ await expect(pending).resolves.toBe("ok");
41
+ ```
42
+
43
+ ## Virtual Time
44
+
45
+ ```ts
46
+ import { makeTestRuntime, sleep, timeout, neverEffect } from "brass-runtime";
47
+
48
+ const { run, runExit, clock, advance } = makeTestRuntime();
49
+
50
+ const sleeping = run(sleep(1_000));
51
+ expect(clock.pendingTimers()).toHaveLength(1);
52
+
53
+ advance(1_000);
54
+ await sleeping;
55
+
56
+ const timedOut = runExit(timeout(neverEffect(), 50));
57
+ advance(50);
58
+ expect(await timedOut).toMatchObject({
59
+ _tag: "Failure",
60
+ cause: { _tag: "Fail", error: { _tag: "TimeoutError", ms: 50 } },
61
+ });
62
+ ```
63
+
64
+ ## Assertion helpers
65
+
66
+ ```ts
67
+ import { assertSucceeds, assertFails, assertFailsWith, assertCompletesWithin } from "brass-runtime";
68
+
69
+ // Assert success with specific value
70
+ await assertSucceeds(myEffect, 42);
71
+
72
+ // Assert failure with specific error
73
+ await assertFails(myEffect, "not found");
74
+
75
+ // Assert failure matching a predicate
76
+ await assertFailsWith(myEffect, (e) => e._tag === "NetworkError");
77
+
78
+ // Assert effect completes within time limit
79
+ const result = await assertCompletesWithin(myEffect, 100); // max 100ms
80
+ ```
81
+
82
+ ## Test effect builders
83
+
84
+ ```ts
85
+ import { flakyEffect, delayedEffect, neverEffect } from "brass-runtime";
86
+
87
+ // Fails N times, then succeeds (for testing retry)
88
+ const flaky = flakyEffect(3, "success!", "temporary error");
89
+ // Call 1: fails with "temporary error"
90
+ // Call 2: fails with "temporary error"
91
+ // Call 3: fails with "temporary error"
92
+ // Call 4: succeeds with "success!"
93
+
94
+ // Completes after a delay (for testing timeouts)
95
+ const slow = delayedEffect(500, "done");
96
+
97
+ // Never completes (for testing interruption)
98
+ const hanging = neverEffect();
99
+ ```
100
+
101
+ ## Testing retry logic
102
+
103
+ ```ts
104
+ import { flakyEffect, retryN, makeTestRuntime } from "brass-runtime";
105
+
106
+ const { run } = makeTestRuntime();
107
+
108
+ it("retries and eventually succeeds", async () => {
109
+ const effect = retryN(flakyEffect(2, "ok", "fail"), 3);
110
+ const result = await run(effect);
111
+ expect(result).toBe("ok");
112
+ });
113
+
114
+ it("fails after exhausting retries", async () => {
115
+ const effect = retryN(flakyEffect(10, "ok", "fail"), 2);
116
+ const exit = await runExit(effect);
117
+ expect(exit._tag).toBe("Failure");
118
+ });
119
+ ```
120
+
121
+ ## Testing timeouts
122
+
123
+ ```ts
124
+ import { timeout, delayedEffect, neverEffect, makeTestRuntime } from "brass-runtime";
125
+
126
+ const { run, advance } = makeTestRuntime();
127
+
128
+ it("succeeds before timeout", async () => {
129
+ const result = run(timeout(delayedEffect(10, "fast"), 1000));
130
+ advance(10);
131
+ await expect(result).resolves.toBe("fast");
132
+ });
133
+
134
+ it("times out on slow effect", async () => {
135
+ const result = run(timeout(neverEffect(), 50));
136
+ advance(50);
137
+ await expect(result).rejects.toMatchObject({ _tag: "TimeoutError", ms: 50 });
138
+ });
139
+ ```
140
+
141
+ ## Testing concurrency
142
+
143
+ ```ts
144
+ import { makeSemaphore, makeTestRuntime, delayedEffect } from "brass-runtime";
145
+
146
+ const { run } = makeTestRuntime();
147
+
148
+ it("limits concurrency", async () => {
149
+ const sem = makeSemaphore(2);
150
+ let maxConcurrent = 0;
151
+ let current = 0;
152
+
153
+ const task = sem.withPermit(async((_env, cb) => {
154
+ current++;
155
+ maxConcurrent = Math.max(maxConcurrent, current);
156
+ setTimeout(() => { current--; cb({ _tag: "Success", value: undefined }); }, 10);
157
+ }));
158
+
159
+ await Promise.all([run(task), run(task), run(task), run(task)]);
160
+ expect(maxConcurrent).toBeLessThanOrEqual(2);
161
+ });
162
+ ```
@@ -0,0 +1,71 @@
1
+ # Tracing
2
+
3
+ OpenTelemetry-compatible span generation for effects.
4
+
5
+ ## Setup
6
+
7
+ ```ts
8
+ import { makeTracer } from "brass-runtime";
9
+
10
+ const tracer = makeTracer({
11
+ serviceName: "my-api",
12
+ sampleRate: 1.0, // sample everything (use 0.1 for 10% in production)
13
+ onSpanEnd: (span) => {
14
+ // Export to your backend (Jaeger, Zipkin, etc.)
15
+ console.log(`[${span.status}] ${span.name}: ${span.endTime! - span.startTime}ms`);
16
+ },
17
+ });
18
+ ```
19
+
20
+ ## Wrapping effects in spans
21
+
22
+ ```ts
23
+ // Wrap any effect in a span
24
+ const result = await run(
25
+ tracer.span("fetchUser", fetchUser(userId), { userId })
26
+ );
27
+
28
+ // Nested spans
29
+ const result = await run(
30
+ tracer.span("handleRequest", asyncFlatMap(
31
+ tracer.span("validateInput", validate(input)),
32
+ (valid) => tracer.span("processData", process(valid))
33
+ ))
34
+ );
35
+ ```
36
+
37
+ ## Attributes
38
+
39
+ ```ts
40
+ tracer.span("db.query", dbQuery(sql), {
41
+ "db.system": "postgresql",
42
+ "db.statement": sql,
43
+ "db.name": "users",
44
+ });
45
+ ```
46
+
47
+ ## Inspecting spans (testing)
48
+
49
+ ```ts
50
+ const tracer = makeTracer({ serviceName: "test" });
51
+
52
+ await run(tracer.span("myOp", asyncSucceed(42)));
53
+
54
+ const spans = tracer.spans();
55
+ expect(spans[0].name).toBe("myOp");
56
+ expect(spans[0].status).toBe("ok");
57
+ expect(spans[0].endTime! - spans[0].startTime).toBeLessThan(10);
58
+
59
+ tracer.clear(); // reset for next test
60
+ ```
61
+
62
+ ## Error spans
63
+
64
+ ```ts
65
+ await run(tracer.span("failingOp", asyncFail("oops"))).catch(() => {});
66
+
67
+ const span = tracer.spans()[0];
68
+ expect(span.status).toBe("error");
69
+ expect(span.events[0].name).toBe("error");
70
+ expect(span.events[0].attributes!["error.message"]).toBe("oops");
71
+ ```