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.
- package/CHANGELOG.md +17 -0
- package/README.md +673 -136
- package/dist/agent/cli/main.cjs +40 -35
- package/dist/agent/cli/main.js +9 -4
- package/dist/agent/cli/main.mjs +9 -4
- package/dist/agent/index.cjs +8 -4
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +7 -3
- package/dist/agent/index.mjs +7 -3
- package/dist/chunk-2HQTDLHF.mjs +683 -0
- package/dist/chunk-36I3M4UC.mjs +370 -0
- package/dist/chunk-3AYM6WPJ.js +1629 -0
- package/dist/chunk-3LOYJFRR.cjs +300 -0
- package/dist/chunk-3RG5ZIWI.js +10 -0
- package/dist/chunk-3Y2RIUMM.js +300 -0
- package/dist/{chunk-VEZNF5GZ.cjs → chunk-4ROBZFL6.cjs} +130 -126
- package/dist/{chunk-3QMOKAS5.js → chunk-52OB2ROS.js} +9 -5
- package/dist/chunk-52PPNNI4.cjs +416 -0
- package/dist/chunk-5EC274J5.cjs +2874 -0
- package/dist/chunk-5QC7LRZ3.js +229 -0
- package/dist/chunk-5VRJNBLZ.mjs +2874 -0
- package/dist/chunk-62AZW6UT.cjs +313 -0
- package/dist/chunk-6IXXWIUM.js +683 -0
- package/dist/chunk-74ZTY6CP.js +2871 -0
- package/dist/chunk-76YMRMH2.cjs +777 -0
- package/dist/chunk-7CMJS3QE.mjs +2871 -0
- package/dist/{chunk-4NHES7VK.mjs → chunk-7JIJOVCT.js} +27 -13
- package/dist/chunk-A2OM6NEH.mjs +194 -0
- package/dist/chunk-AGR5B2BC.cjs +683 -0
- package/dist/chunk-AVNQLJ5V.js +777 -0
- package/dist/chunk-B33ICAKP.js +313 -0
- package/dist/{chunk-ELOOF35R.mjs → chunk-B5JD23U7.mjs} +1 -1
- package/dist/chunk-BABBZK4Y.js +2024 -0
- package/dist/chunk-C3MDXTRZ.js +354 -0
- package/dist/chunk-CIZFIMK5.js +2193 -0
- package/dist/chunk-CZIVE6NT.cjs +354 -0
- package/dist/chunk-DNFJLJMW.mjs +354 -0
- package/dist/chunk-DNFO2EIZ.mjs +777 -0
- package/dist/chunk-EJ6BPYVR.mjs +416 -0
- package/dist/chunk-ENKODRU3.cjs +2193 -0
- package/dist/chunk-EOC4UHBS.mjs +229 -0
- package/dist/{chunk-BMH5AV44.js → chunk-FH2X7BVP.js} +756 -440
- package/dist/{chunk-PPUXIH5R.js → chunk-FHQGHPMO.mjs} +27 -13
- package/dist/{chunk-TGIFUAK4.cjs → chunk-GLE2WY7Z.cjs} +951 -635
- package/dist/{chunk-BDF4AMWX.mjs → chunk-GYM3LLGS.mjs} +756 -440
- package/dist/chunk-HLWLMW2F.mjs +2024 -0
- package/dist/chunk-JF5WGYJJ.cjs +194 -0
- package/dist/chunk-KH4SYAOS.mjs +1629 -0
- package/dist/chunk-KN32XNTH.mjs +313 -0
- package/dist/chunk-KQLYONSE.cjs +2871 -0
- package/dist/{chunk-STVLQ3XD.cjs → chunk-KZJQ723N.cjs} +92 -78
- package/dist/chunk-L2SYFEBS.js +194 -0
- package/dist/chunk-L6VB5N7Q.cjs +104 -0
- package/dist/{chunk-K6M7MDZ4.mjs → chunk-MBEJI5HF.mjs} +9 -5
- package/dist/chunk-MIIYDLGM.js +2874 -0
- package/dist/chunk-MOO4L7F4.mjs +104 -0
- package/dist/chunk-MT3OWDPC.mjs +2193 -0
- package/dist/chunk-MVGUEJ5Z.cjs +370 -0
- package/dist/chunk-OBGZSXTJ.cjs +10 -0
- package/dist/chunk-PD4EJTQC.cjs +229 -0
- package/dist/chunk-PWC3RBQE.mjs +300 -0
- package/dist/chunk-Q2I37RP3.cjs +1629 -0
- package/dist/chunk-RKGKFN2A.js +416 -0
- package/dist/{chunk-R3R2FVLG.cjs → chunk-SA6HUJVI.cjs} +5 -5
- package/dist/chunk-TRM4JUZQ.js +104 -0
- package/dist/chunk-UB4B6OFY.js +370 -0
- package/dist/{chunk-TO7IKXYT.js → chunk-UCUBNWM2.js} +1 -1
- package/dist/chunk-VN44DYYT.cjs +2024 -0
- package/dist/chunk-Y6FXYEAI.mjs +10 -0
- package/dist/client-CZHU674n.d.ts +820 -0
- package/dist/core/index.cjs +198 -4
- package/dist/core/index.d.ts +311 -212
- package/dist/core/index.js +237 -43
- package/dist/core/index.mjs +237 -43
- package/dist/{effect-CMOQKX8y.d.ts → effect-DIUHZ9IN.d.ts} +195 -1
- package/dist/effectRunner-CFLC32IK.cjs +8 -0
- package/dist/effectRunner-L4S7IPT3.js +8 -0
- package/dist/effectRunner-NNGG75QA.mjs +8 -0
- package/dist/http/index.cjs +1227 -2971
- package/dist/http/index.d.ts +826 -280
- package/dist/http/index.js +1089 -2833
- package/dist/http/index.mjs +1089 -2833
- package/dist/http/testing.cjs +161 -0
- package/dist/http/testing.d.ts +43 -0
- package/dist/http/testing.js +161 -0
- package/dist/http/testing.mjs +161 -0
- package/dist/index.cjs +486 -250
- package/dist/index.d.ts +87 -95
- package/dist/index.js +391 -155
- package/dist/index.mjs +391 -155
- package/dist/observability/index.cjs +162 -0
- package/dist/observability/index.d.ts +152 -0
- package/dist/observability/index.js +162 -0
- package/dist/observability/index.mjs +162 -0
- package/dist/perf/cli.cjs +401 -0
- package/dist/perf/cli.d.ts +1 -0
- package/dist/perf/cli.js +401 -0
- package/dist/perf/cli.mjs +401 -0
- package/dist/perf/index.cjs +141 -0
- package/dist/perf/index.d.ts +483 -0
- package/dist/perf/index.js +141 -0
- package/dist/perf/index.mjs +141 -0
- package/dist/schedule-CK3Ml_7p.d.ts +259 -0
- package/dist/schema/index.cjs +29 -0
- package/dist/schema/index.d.ts +179 -0
- package/dist/schema/index.js +29 -0
- package/dist/schema/index.mjs +29 -0
- package/dist/server-GJPg8ZSG.d.ts +675 -0
- package/dist/{stream-FQm9h4Mg.d.ts → stream-B4oK9JFP.d.ts} +1 -1
- package/dist/tracer-Hwt1cl7h.d.ts +189 -0
- package/dist/tracing-DqbTKGcf.d.ts +148 -0
- package/docs/ARCHITECTURE.md +292 -0
- package/docs/README.md +63 -0
- package/docs/adr/0001-ai-context-pack.md +32 -0
- package/docs/agent-apply-mode.md +104 -0
- package/docs/agent-approvals.md +110 -0
- package/docs/agent-batch.md +185 -0
- package/docs/agent-boundaries.md +112 -0
- package/docs/agent-chat-sessions.md +160 -0
- package/docs/agent-ci.md +17 -0
- package/docs/agent-cli.md +405 -0
- package/docs/agent-config.md +480 -0
- package/docs/agent-context-discovery.md +159 -0
- package/docs/agent-copilot-like-dx.md +126 -0
- package/docs/agent-declarative-optimized-planning.md +138 -0
- package/docs/agent-dx.md +224 -0
- package/docs/agent-env-files.md +126 -0
- package/docs/agent-follow-up-context.md +43 -0
- package/docs/agent-global-usage.md +180 -0
- package/docs/agent-init.md +109 -0
- package/docs/agent-install-and-configure.md +516 -0
- package/docs/agent-language-workspace-ux.md +99 -0
- package/docs/agent-llm-adapters.md +123 -0
- package/docs/agent-local-install.md +190 -0
- package/docs/agent-local-tests.md +51 -0
- package/docs/agent-observability.md +155 -0
- package/docs/agent-patch-quality-loop.md +162 -0
- package/docs/agent-presets.md +22 -0
- package/docs/agent-project-commands.md +237 -0
- package/docs/agent-project-intelligence.md +156 -0
- package/docs/agent-redaction.md +18 -0
- package/docs/agent-release-readiness.md +76 -0
- package/docs/agent-rollback-safety.md +162 -0
- package/docs/agent-rollback.md +23 -0
- package/docs/agent-run-artifacts.md +16 -0
- package/docs/agent-vscode-auto-discovery.md +137 -0
- package/docs/agent-vscode-batch-runner.md +100 -0
- package/docs/agent-vscode-chat-layout.md +90 -0
- package/docs/agent-vscode-clean-install.md +147 -0
- package/docs/agent-vscode-code-actions.md +70 -0
- package/docs/agent-vscode-diff-preview.md +45 -0
- package/docs/agent-vscode-inline-assist.md +56 -0
- package/docs/agent-vscode-install.md +186 -0
- package/docs/agent-vscode-model-setup.md +97 -0
- package/docs/agent-vscode-patch-preview.md +92 -0
- package/docs/agent-vscode-problems.md +79 -0
- package/docs/agent-vscode-project-dashboard.md +106 -0
- package/docs/agent-vscode-run-history.md +92 -0
- package/docs/agent-vscode-ux.md +73 -0
- package/docs/ai/INVARIANTS.md +84 -0
- package/docs/ai/PROJECT_MAP.md +338 -0
- package/docs/ai/PUBLIC_API.md +336 -0
- package/docs/ai/VALIDATION_MATRIX.md +67 -0
- package/docs/api-polish.md +37 -0
- package/docs/cancellation.md +162 -0
- package/docs/coverage.md +46 -0
- package/docs/getting-started.md +159 -0
- package/docs/guides/README.md +40 -0
- package/docs/guides/circuit-breaker.md +89 -0
- package/docs/guides/error-handling.md +91 -0
- package/docs/guides/getting-started.md +107 -0
- package/docs/guides/layers.md +189 -0
- package/docs/guides/metrics.md +101 -0
- package/docs/guides/resource-management.md +141 -0
- package/docs/guides/retry.md +215 -0
- package/docs/guides/semaphore.md +66 -0
- package/docs/guides/streams.md +117 -0
- package/docs/guides/supervisors.md +98 -0
- package/docs/guides/testing.md +162 -0
- package/docs/guides/tracing.md +71 -0
- package/docs/http-recipes.md +399 -0
- package/docs/http.md +749 -0
- package/docs/modules.md +285 -0
- package/docs/observability-collector-smoke.md +31 -0
- package/docs/observability-framework-examples.md +98 -0
- package/docs/observability.md +542 -0
- package/docs/otel-collector-smoke.yaml +27 -0
- package/docs/performance-profiler.md +199 -0
- package/docs/production-readiness.md +73 -0
- package/docs/recipes/README.md +12 -0
- package/docs/recipes/http-server.md +45 -0
- package/docs/recipes/layers.md +44 -0
- package/docs/recipes/performance.md +47 -0
- package/docs/recipes/runtime.md +41 -0
- package/docs/recipes/testing.md +41 -0
- package/docs/release.md +53 -0
- package/docs/wasm-bounded-queues.md +44 -0
- package/docs/wasm-engine-observability-benchmarks.md +85 -0
- package/docs/wasm-fiber-engine.md +117 -0
- package/docs/wasm-scheduler-state-machine.md +122 -0
- package/docs/wasm-stream-chunks.md +54 -0
- package/package.json +48 -2
- package/dist/chunk-AR22SXML.js +0 -1043
- package/dist/chunk-BDYEENHT.js +0 -224
- package/dist/chunk-JFPU5GQI.mjs +0 -1043
- package/dist/chunk-MS34J5LY.cjs +0 -224
- package/dist/chunk-UMAZLXAB.mjs +0 -224
- package/dist/chunk-XPZNXSVN.cjs +0 -1043
- 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
|
+
```
|