brass-runtime 1.16.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 +283 -18
- package/dist/agent/cli/main.cjs +38 -38
- package/dist/agent/cli/main.js +6 -6
- package/dist/agent/cli/main.mjs +6 -6
- package/dist/agent/index.cjs +7 -7
- package/dist/agent/index.d.ts +1 -1
- package/dist/agent/index.js +6 -6
- package/dist/agent/index.mjs +6 -6
- package/dist/chunk-2HQTDLHF.mjs +683 -0
- package/dist/chunk-36I3M4UC.mjs +370 -0
- package/dist/{chunk-QY5FKYEQ.js → chunk-3AYM6WPJ.js} +570 -51
- package/dist/chunk-3LOYJFRR.cjs +300 -0
- package/dist/chunk-3Y2RIUMM.js +300 -0
- package/dist/{chunk-N6VHMOWB.cjs → chunk-4ROBZFL6.cjs} +128 -128
- package/dist/{chunk-NC5SDRYE.js → chunk-52OB2ROS.js} +4 -4
- package/dist/{chunk-JX3LZQJH.cjs → chunk-52PPNNI4.cjs} +82 -20
- package/dist/{chunk-5YOQOXEQ.cjs → chunk-5EC274J5.cjs} +676 -293
- package/dist/chunk-5QC7LRZ3.js +229 -0
- package/dist/{chunk-7TL2LHQJ.js → chunk-5VRJNBLZ.mjs} +524 -141
- 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-2WC63LJK.mjs → chunk-7JIJOVCT.js} +20 -10
- package/dist/{chunk-FM4W4QPL.js → chunk-A2OM6NEH.mjs} +5 -4
- package/dist/chunk-AGR5B2BC.cjs +683 -0
- package/dist/chunk-AVNQLJ5V.js +777 -0
- package/dist/chunk-B33ICAKP.js +313 -0
- package/dist/{chunk-J3H54ZRV.mjs → chunk-B5JD23U7.mjs} +1 -1
- package/dist/chunk-BABBZK4Y.js +2024 -0
- package/dist/{chunk-U5KWK3PX.mjs → chunk-C3MDXTRZ.js} +11 -0
- package/dist/{chunk-F5EUMJL7.mjs → chunk-CIZFIMK5.js} +55 -5
- package/dist/{chunk-SPUEME2B.cjs → chunk-CZIVE6NT.cjs} +12 -1
- package/dist/{chunk-TDVMADDN.js → chunk-DNFJLJMW.mjs} +11 -0
- package/dist/chunk-DNFO2EIZ.mjs +777 -0
- package/dist/{chunk-XDZOO4L5.js → chunk-EJ6BPYVR.mjs} +79 -17
- package/dist/{chunk-JNFRRJYH.cjs → chunk-ENKODRU3.cjs} +242 -192
- package/dist/chunk-EOC4UHBS.mjs +229 -0
- package/dist/{chunk-7LVI2GIN.js → chunk-FH2X7BVP.js} +507 -72
- package/dist/{chunk-OOGJ73B6.js → chunk-FHQGHPMO.mjs} +20 -10
- package/dist/{chunk-WQ5QNU5R.cjs → chunk-GLE2WY7Z.cjs} +652 -217
- package/dist/{chunk-G6IQOE4P.mjs → chunk-GYM3LLGS.mjs} +507 -72
- package/dist/chunk-HLWLMW2F.mjs +2024 -0
- package/dist/{chunk-TVN5I4U6.cjs → chunk-JF5WGYJJ.cjs} +25 -24
- package/dist/{chunk-CY33PGEX.mjs → chunk-KH4SYAOS.mjs} +570 -51
- package/dist/chunk-KN32XNTH.mjs +313 -0
- package/dist/chunk-KQLYONSE.cjs +2871 -0
- package/dist/{chunk-7HUOJA4W.cjs → chunk-KZJQ723N.cjs} +90 -80
- package/dist/{chunk-CCKHV5BT.mjs → chunk-L2SYFEBS.js} +5 -4
- package/dist/{chunk-IJT6RRQ5.cjs → chunk-L6VB5N7Q.cjs} +20 -9
- package/dist/{chunk-ZGLD4TVZ.mjs → chunk-MBEJI5HF.mjs} +4 -4
- package/dist/{chunk-PRWCB3QL.mjs → chunk-MIIYDLGM.js} +524 -141
- package/dist/{chunk-H55LI6WY.js → chunk-MOO4L7F4.mjs} +15 -4
- package/dist/{chunk-7XOPAB5Q.js → chunk-MT3OWDPC.mjs} +55 -5
- package/dist/chunk-MVGUEJ5Z.cjs +370 -0
- package/dist/chunk-PD4EJTQC.cjs +229 -0
- package/dist/chunk-PWC3RBQE.mjs +300 -0
- package/dist/{chunk-MWXMNYJS.cjs → chunk-Q2I37RP3.cjs} +643 -124
- package/dist/{chunk-VFIUZG7J.mjs → chunk-RKGKFN2A.js} +79 -17
- package/dist/{chunk-NYL4D7SK.cjs → chunk-SA6HUJVI.cjs} +5 -5
- package/dist/{chunk-K2T3DV26.mjs → chunk-TRM4JUZQ.js} +15 -4
- package/dist/chunk-UB4B6OFY.js +370 -0
- package/dist/{chunk-G3XGCZDQ.js → chunk-UCUBNWM2.js} +1 -1
- package/dist/chunk-VN44DYYT.cjs +2024 -0
- package/dist/{client-CtFmoDvM.d.ts → client-CZHU674n.d.ts} +211 -36
- package/dist/core/index.cjs +135 -9
- package/dist/core/index.d.ts +238 -33
- package/dist/core/index.js +155 -29
- package/dist/core/index.mjs +155 -29
- package/dist/{effect-CGNl5Rqp.d.ts → effect-DIUHZ9IN.d.ts} +89 -1
- package/dist/effectRunner-CFLC32IK.cjs +8 -0
- package/dist/{effectRunner-A4CHJXJI.js → effectRunner-L4S7IPT3.js} +2 -2
- package/dist/{effectRunner-OPUF6QRN.mjs → effectRunner-NNGG75QA.mjs} +2 -2
- package/dist/http/index.cjs +324 -2986
- package/dist/http/index.d.ts +54 -68
- package/dist/http/index.js +238 -2900
- package/dist/http/index.mjs +238 -2900
- package/dist/http/testing.cjs +14 -12
- package/dist/http/testing.d.ts +5 -4
- package/dist/http/testing.js +10 -8
- package/dist/http/testing.mjs +10 -8
- package/dist/index.cjs +423 -255
- package/dist/index.d.ts +87 -69
- package/dist/index.js +301 -133
- package/dist/index.mjs +301 -133
- package/dist/observability/index.cjs +16 -531
- package/dist/observability/index.d.ts +81 -8
- package/dist/observability/index.js +23 -538
- package/dist/observability/index.mjs +23 -538
- 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 +6 -2
- package/dist/schema/index.d.ts +3 -1
- package/dist/schema/index.js +5 -1
- package/dist/schema/index.mjs +5 -1
- package/dist/{server-C8hDXA74.d.ts → server-GJPg8ZSG.d.ts} +4 -3
- package/dist/{stream-dvSs0QS5.d.ts → stream-B4oK9JFP.d.ts} +1 -1
- package/dist/{tracer-B5tRH9H7.d.ts → tracer-Hwt1cl7h.d.ts} +13 -54
- package/dist/{tracing-Dt9S_6V8.d.ts → tracing-DqbTKGcf.d.ts} +1 -1
- 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 +22 -2
- package/dist/chunk-45F7OKGT.cjs +0 -104
- package/dist/chunk-7V4KY4RL.mjs +0 -104
- package/dist/chunk-DJQ7OMMB.cjs +0 -144
- package/dist/chunk-GOV47PPB.mjs +0 -552
- package/dist/chunk-JF4XXPZ5.cjs +0 -552
- package/dist/chunk-KCPT2D6G.js +0 -552
- package/dist/chunk-NOYZIMUJ.mjs +0 -144
- package/dist/chunk-PNVFW245.js +0 -144
- package/dist/chunk-ROJC3NBJ.js +0 -104
- package/dist/effectRunner-3ZHAD3LE.cjs +0 -8
- package/dist/schedule-Fque9Abz.d.ts +0 -70
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# Brass Runtime WASM Fiber Engine
|
|
2
|
+
|
|
3
|
+
Este repo mantiene la estructura original de `brass-runtime`: el package se publica desde la raíz del repositorio, no desde `packages/brass-runtime`.
|
|
4
|
+
|
|
5
|
+
## Qué cambió
|
|
6
|
+
|
|
7
|
+
Se agregó un engine interno experimental para mover el estado de suspensión de las fibras hacia un bridge WASM:
|
|
8
|
+
|
|
9
|
+
- `src/core/runtime/engine/types.ts`: contrato `FiberEngine`.
|
|
10
|
+
- `src/core/runtime/engine/JsFiberEngine.ts`: fallback JS que usa el `RuntimeFiber` actual.
|
|
11
|
+
- `src/core/runtime/engine/WasmFiberEngine.ts`: engine experimental JS/WASM.
|
|
12
|
+
- `src/core/runtime/engine/opcodes.ts`: representación interna del `Async` actual como plan/opcodes.
|
|
13
|
+
- `src/core/runtime/engine/bridge/ReferenceWasmBridge.ts`: bridge de referencia, sin compilar Rust, útil para tests locales.
|
|
14
|
+
- `src/core/runtime/engine/bridge/WasmPackFiberBridge.ts`: adapter para el output de `wasm-pack`.
|
|
15
|
+
- `src/core/runtime/hostAction.ts`: efectos host declarativos HTTP/DB/Queue/custom.
|
|
16
|
+
- `crates/brass-runtime-wasm-engine`: crate Rust/WASM.
|
|
17
|
+
- `src/core/runtime/bench/heap-per-suspended-fiber.ts`: benchmark de heap por fibra suspendida.
|
|
18
|
+
|
|
19
|
+
## Uso
|
|
20
|
+
|
|
21
|
+
El modo por defecto sigue siendo JS:
|
|
22
|
+
|
|
23
|
+
```ts
|
|
24
|
+
const runtime = Runtime.make({});
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Modo experimental sin compilar Rust:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
const runtime = new Runtime({
|
|
31
|
+
env: {},
|
|
32
|
+
engine: "wasm-reference",
|
|
33
|
+
});
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Modo WASM real:
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
const runtime = new Runtime({
|
|
40
|
+
env: {},
|
|
41
|
+
engine: "wasm",
|
|
42
|
+
});
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Host actions declarativas
|
|
46
|
+
|
|
47
|
+
```ts
|
|
48
|
+
import { Runtime, fromHostAction } from "brass-runtime";
|
|
49
|
+
|
|
50
|
+
const runtime = new Runtime({
|
|
51
|
+
env: {},
|
|
52
|
+
engine: "wasm-reference",
|
|
53
|
+
hostExecutor: {
|
|
54
|
+
async execute(action, ctx) {
|
|
55
|
+
// Acá vive fetch/axios/db/queue real.
|
|
56
|
+
return { kind: "ok", value: { ok: true } };
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result = await runtime.toPromise(
|
|
62
|
+
fromHostAction({ kind: "custom", target: "my-side-effect" })
|
|
63
|
+
);
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Con `fromPromiseAbortable`, la closure host sigue viviendo en JS porque Axios/fetch/DB viven en Node. El engine WASM reduce la parte de continuación/scheduler/fiber state de Brass. Para reducir más heap por suspensión, usá `fromHostAction`, que representa el efecto como acción declarativa y deja el side effect real en el `HostExecutor`.
|
|
67
|
+
|
|
68
|
+
## Publicación desde este repo
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
npm install
|
|
72
|
+
npm run build
|
|
73
|
+
npm pack --dry-run
|
|
74
|
+
npm publish
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Si querés publicar con el `.wasm` real incluido:
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
cargo --version
|
|
81
|
+
wasm-pack --version
|
|
82
|
+
npm run build:wasm
|
|
83
|
+
npm run build
|
|
84
|
+
npm pack --dry-run
|
|
85
|
+
npm publish
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
## Benchmark
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npm run benchmark heap-suspended-fiber
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
The direct benchmark script is still available when you want a specific engine
|
|
95
|
+
or longer suspension window:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
node --expose-gc ./node_modules/.bin/tsx src/core/runtime/bench/heap-per-suspended-fiber.ts \
|
|
99
|
+
--engine wasm \
|
|
100
|
+
--mode host-action \
|
|
101
|
+
--fibers 10000 \
|
|
102
|
+
--delayMs 2000
|
|
103
|
+
node --expose-gc ./node_modules/.bin/tsx src/core/runtime/bench/heap-per-suspended-fiber.ts --engine ts --mode closure
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## WASM fiber registry + wakeups
|
|
107
|
+
|
|
108
|
+
The WASM engine now also owns a compact `BrassWasmFiberRegistry` used by the TS engine facade to track fiber lifecycle metadata and coalesce wakeups.
|
|
109
|
+
|
|
110
|
+
The public TypeScript `Runtime` API is unchanged. When `engine: "wasm"` is selected:
|
|
111
|
+
|
|
112
|
+
- TS still executes host callbacks, promises, finalizers and Node integrations.
|
|
113
|
+
- Rust/WASM tracks fiber states: queued, running, suspended, done, failed and interrupted.
|
|
114
|
+
- Rust/WASM owns a wakeup queue so repeated wakeups for the same fiber are coalesced before TS schedules more work.
|
|
115
|
+
- `runtime.stats().fiberRegistry` exposes registry counters for observability.
|
|
116
|
+
|
|
117
|
+
This keeps the high-level Brass API ergonomic while moving another hot coordination path into a compact Rust state machine.
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# WASM scheduler state machine
|
|
2
|
+
|
|
3
|
+
The scheduler bookkeeping hot path lives in `brass-runtime-wasm-engine` when the scheduler is created with `engine: "wasm"` or `engine: "auto"` and the WASM package is available. The public TypeScript scheduler API remains unchanged.
|
|
4
|
+
|
|
5
|
+
## What moved to Rust/WASM
|
|
6
|
+
|
|
7
|
+
- scheduler phase: `idle`, `scheduledMicro`, `scheduledMacro`, `flushing`
|
|
8
|
+
- inferred caller lanes from task tags
|
|
9
|
+
- one bounded task-ref queue per lane
|
|
10
|
+
- round-robin lane rotation
|
|
11
|
+
- per-lane execution budget
|
|
12
|
+
- global flush budget accounting
|
|
13
|
+
- micro vs macro scheduling policy
|
|
14
|
+
- global counters for enqueued/executed/dropped tasks
|
|
15
|
+
- per-lane counters for enqueued/executed/dropped tasks
|
|
16
|
+
- counters for budget yields and flush cycles
|
|
17
|
+
|
|
18
|
+
## What stays in TypeScript
|
|
19
|
+
|
|
20
|
+
- the actual task callbacks
|
|
21
|
+
- `queueMicrotask`, `setImmediate`, `MessageChannel`, `setTimeout`
|
|
22
|
+
- error logging
|
|
23
|
+
- Node/browser integration
|
|
24
|
+
|
|
25
|
+
This is intentional: WASM coordinates scheduler state and returns task refs, but JS still owns and executes side effects.
|
|
26
|
+
|
|
27
|
+
## Lane inference
|
|
28
|
+
|
|
29
|
+
Rust and TypeScript now use the same tag convention:
|
|
30
|
+
|
|
31
|
+
```txt
|
|
32
|
+
lane:<caller>|<task-label>
|
|
33
|
+
caller:<caller>|<task-label>
|
|
34
|
+
<fallback-prefix>#<id>.<task-label>
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
`Runtime.withLane("bff-security/generate")` ultimately schedules fibers with a `lane:` tag, so Brass does not need to know anything about the BFF implementation. It only sees a stable caller key.
|
|
38
|
+
|
|
39
|
+
When no explicit lane is set, the TypeScript runtime infers a lane once from the first non-Brass callsite and then passes that stable lane to JS or WASM scheduler state. Child fibers inherit their parent lane.
|
|
40
|
+
|
|
41
|
+
## Bounded queues and budgets
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const scheduler = new Scheduler({
|
|
45
|
+
engine: "wasm",
|
|
46
|
+
laneCapacity: 512,
|
|
47
|
+
laneBudget: 32,
|
|
48
|
+
flushBudget: 1024,
|
|
49
|
+
maxLanes: 256,
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Semantics:
|
|
54
|
+
|
|
55
|
+
- `laneCapacity`: max queued task refs per lane. When a lane is full, the newly enqueued task is dropped, `droppedTasks` increments, and TS maps the Rust policy to `Scheduler.schedule() === "dropped"`. Runtime fibers fail fast rather than hanging.
|
|
56
|
+
- `laneBudget`: max tasks a single lane may run before the scheduler rotates to the next non-empty lane.
|
|
57
|
+
- `flushBudget`: max tasks per flush before yielding back to the event loop.
|
|
58
|
+
- `maxLanes`: max distinct inferred lanes before new unknown callers are grouped into `overflow`.
|
|
59
|
+
|
|
60
|
+
## Usage
|
|
61
|
+
|
|
62
|
+
```ts
|
|
63
|
+
const scheduler = new Scheduler({ engine: "wasm" });
|
|
64
|
+
const runtime = Runtime.make({}, scheduler).withLane("bff-security/validate");
|
|
65
|
+
|
|
66
|
+
await runtime.toPromise(effect);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
For production-safe behavior use auto mode:
|
|
70
|
+
|
|
71
|
+
```ts
|
|
72
|
+
const scheduler = new Scheduler({ engine: "auto" });
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`auto` uses WASM when `wasm/pkg/brass_runtime_wasm_engine.js` is available and falls back to the JS implementation otherwise.
|
|
76
|
+
|
|
77
|
+
## Introspection
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
console.log(scheduler.stats());
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
The WASM path returns counters from the Rust state machine:
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
{
|
|
87
|
+
engine: "wasm",
|
|
88
|
+
fallbackUsed: false,
|
|
89
|
+
data: {
|
|
90
|
+
phase: "idle",
|
|
91
|
+
len: 0,
|
|
92
|
+
capacity: 512,
|
|
93
|
+
scheduledFlushes: 10,
|
|
94
|
+
completedFlushes: 10,
|
|
95
|
+
enqueuedTasks: 1000,
|
|
96
|
+
executedTasks: 1000,
|
|
97
|
+
droppedTasks: 0,
|
|
98
|
+
yieldedByBudget: 0,
|
|
99
|
+
lanes: [
|
|
100
|
+
{
|
|
101
|
+
key: "bff-security/validate",
|
|
102
|
+
len: 0,
|
|
103
|
+
capacity: 512,
|
|
104
|
+
enqueuedTasks: 1000,
|
|
105
|
+
executedTasks: 1000,
|
|
106
|
+
droppedTasks: 0
|
|
107
|
+
}
|
|
108
|
+
]
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Build
|
|
114
|
+
|
|
115
|
+
After changing Rust, rebuild the WASM package locally:
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
npm run build:wasm
|
|
119
|
+
npm run build
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
The TypeScript changes compile without requiring the WASM artifact, but `engine: "wasm"` requires the generated `wasm/pkg` output at runtime.
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# WASM stream chunks
|
|
2
|
+
|
|
3
|
+
This adds an optional WASM-backed chunk buffer for `ZStream`.
|
|
4
|
+
|
|
5
|
+
The goal is not to move all stream semantics to Rust. The goal is to make the hot path chunk-aware so downstream operators can process arrays/batches instead of one element at a time.
|
|
6
|
+
|
|
7
|
+
## API
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { chunks, chunksP, mapChunksEffectP } from "brass-runtime";
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Direct stream API
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
const chunked = chunks(stream, 1024, { engine: "wasm" });
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
`chunked` has type:
|
|
20
|
+
|
|
21
|
+
```ts
|
|
22
|
+
ZStream<R, E, readonly A[]>
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Pipeline API
|
|
26
|
+
|
|
27
|
+
```ts
|
|
28
|
+
const batched = via(stream, chunksP<number>(1024, { engine: "wasm" }));
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### Effectful chunk mapping
|
|
32
|
+
|
|
33
|
+
```ts
|
|
34
|
+
const processed = via(
|
|
35
|
+
stream,
|
|
36
|
+
mapChunksEffectP(1024, (chunk) =>
|
|
37
|
+
asyncSucceed(chunk.map((n) => n * 2))
|
|
38
|
+
, { engine: "wasm" })
|
|
39
|
+
);
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Engine modes
|
|
43
|
+
|
|
44
|
+
```ts
|
|
45
|
+
{ engine: "js" } // always JS arrays
|
|
46
|
+
{ engine: "wasm" } // require wasm/pkg
|
|
47
|
+
{ engine: "auto" } // WASM when available, otherwise JS fallback
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Why this shape
|
|
51
|
+
|
|
52
|
+
Crossing Node/JS ↔ WASM for every element can be slower than plain JS. This implementation makes WASM useful as a chunk boundary and gives the rest of the stream pipeline a batch-oriented representation.
|
|
53
|
+
|
|
54
|
+
The next optimization step would be to add specialized Rust operations for primitive chunks, for example numeric `sum`, `min`, `max`, `filterGt`, or binary/typed-array transforms. Generic JS callbacks should stay in TS.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "brass-runtime",
|
|
3
|
-
"version": "1.16.
|
|
3
|
+
"version": "1.16.1",
|
|
4
4
|
"description": "Effect runtime utilities for TypeScript",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Augusto Vivaldelli",
|
|
@@ -38,6 +38,11 @@
|
|
|
38
38
|
"import": "./dist/observability/index.mjs",
|
|
39
39
|
"require": "./dist/observability/index.cjs"
|
|
40
40
|
},
|
|
41
|
+
"./perf": {
|
|
42
|
+
"types": "./dist/perf/index.d.ts",
|
|
43
|
+
"import": "./dist/perf/index.mjs",
|
|
44
|
+
"require": "./dist/perf/index.cjs"
|
|
45
|
+
},
|
|
41
46
|
"./agent": {
|
|
42
47
|
"types": "./dist/agent/index.d.ts",
|
|
43
48
|
"import": "./dist/agent/index.mjs",
|
|
@@ -51,6 +56,8 @@
|
|
|
51
56
|
"dist",
|
|
52
57
|
"wasm/pkg",
|
|
53
58
|
"README.md",
|
|
59
|
+
"CHANGELOG.md",
|
|
60
|
+
"docs",
|
|
54
61
|
"LICENSE",
|
|
55
62
|
"package.json"
|
|
56
63
|
],
|
|
@@ -106,13 +113,25 @@
|
|
|
106
113
|
"agent:vscode:reinstall:global": "npm run agent:vscode:uninstall:global && npm run agent:vscode:install:global",
|
|
107
114
|
"benchmark": "tsx src/benchmarks/runner.ts",
|
|
108
115
|
"benchmark:json": "tsx src/benchmarks/runner.ts --json",
|
|
116
|
+
"benchmark:runtime": "tsx src/benchmarks/runner.ts runtime-performance-track",
|
|
117
|
+
"benchmark:runtime:budget": "node scripts/check-runtime-benchmark-budget.mjs",
|
|
109
118
|
"benchmark:http": "tsx src/benchmarks/runner.ts http-concurrent",
|
|
110
119
|
"benchmark:http:soak": "BRASS_HTTP_BENCH_MODE=soak tsx src/benchmarks/runner.ts http-concurrent",
|
|
111
120
|
"benchmark:http:budget": "node scripts/check-http-benchmark-budget.mjs",
|
|
121
|
+
"benchmark:http:ramp": "tsx src/benchmarks/runner.ts http-ramp-tps",
|
|
112
122
|
"benchmark:adaptive": "tsx src/benchmarks/runner.ts adaptive-limiter-soak",
|
|
113
123
|
"benchmark:adaptive:soak": "BRASS_ADAPTIVE_BENCH_MODE=soak tsx src/benchmarks/runner.ts adaptive-limiter-soak",
|
|
114
124
|
"benchmark:observability": "tsx src/benchmarks/runner.ts observability-overhead",
|
|
115
125
|
"benchmark:observability:budget": "node scripts/check-observability-benchmark-budget.mjs",
|
|
126
|
+
"benchmark:perf": "tsx src/perf/cli.ts --json",
|
|
127
|
+
"perf": "tsx src/perf/cli.ts",
|
|
128
|
+
"perf:json": "tsx src/perf/cli.ts --json",
|
|
129
|
+
"perf:runtime:ab": "tsx src/perf/cli.ts --profile runtime-ab",
|
|
130
|
+
"perf:runtime:soak": "tsx src/perf/cli.ts --profile runtime-soak",
|
|
131
|
+
"perf:runtime:budget": "tsx src/perf/budgetCli.ts",
|
|
132
|
+
"perf:http:memory": "tsx src/perf/cli.ts --profile http-memory",
|
|
133
|
+
"perf:history": "tsx src/perf/cli.ts --record-history",
|
|
134
|
+
"release:check": "npm run test:types && npm test && npm run build:ts && npm run validate:cjs && npm run perf:runtime:budget && npm run benchmark:runtime:budget && npm run benchmark:http:budget && npm run benchmark:observability:budget",
|
|
116
135
|
"example:observability:express": "tsx src/examples/observabilityExpress.ts",
|
|
117
136
|
"example:observability:fastify": "tsx src/examples/observabilityFastify.ts",
|
|
118
137
|
"example:observability:nest": "tsx src/examples/observabilityNest.ts",
|
|
@@ -143,6 +162,7 @@
|
|
|
143
162
|
},
|
|
144
163
|
"homepage": "https://github.com/BaldrVivaldelli/brass-runtime#readme",
|
|
145
164
|
"bin": {
|
|
146
|
-
"brass-agent": "dist/agent/cli/main.cjs"
|
|
165
|
+
"brass-agent": "dist/agent/cli/main.cjs",
|
|
166
|
+
"brass-perf": "dist/perf/cli.cjs"
|
|
147
167
|
}
|
|
148
168
|
}
|
package/dist/chunk-45F7OKGT.cjs
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
"use strict";Object.defineProperty(exports, "__esModule", {value: true}); function _nullishCoalesce(lhs, rhsFn) { if (lhs != null) { return lhs; } else { return rhsFn(); } }
|
|
2
|
-
|
|
3
|
-
var _chunkWQ5QNU5Rcjs = require('./chunk-WQ5QNU5R.cjs');
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
var _chunkDJQ7OMMBcjs = require('./chunk-DJQ7OMMB.cjs');
|
|
11
|
-
|
|
12
|
-
// src/core/runtime/combinators.ts
|
|
13
|
-
function sleep(ms) {
|
|
14
|
-
return _chunkDJQ7OMMBcjs.asyncEffect.call(void 0, (_env, cb) => {
|
|
15
|
-
const delay = Math.max(0, Math.floor(ms));
|
|
16
|
-
const id = setTimeout(() => cb({ _tag: "Success", value: void 0 }), delay);
|
|
17
|
-
return () => clearTimeout(id);
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
function timeout(effect, ms) {
|
|
21
|
-
return _chunkDJQ7OMMBcjs.asyncEffect.call(void 0, (env, cb) => {
|
|
22
|
-
let done = false;
|
|
23
|
-
let timerId;
|
|
24
|
-
let effectRunning = true;
|
|
25
|
-
timerId = setTimeout(() => {
|
|
26
|
-
if (done) return;
|
|
27
|
-
done = true;
|
|
28
|
-
effectRunning = false;
|
|
29
|
-
cb({
|
|
30
|
-
_tag: "Failure",
|
|
31
|
-
cause: { _tag: "Fail", error: { _tag: "TimeoutError", ms } }
|
|
32
|
-
});
|
|
33
|
-
}, Math.max(0, Math.floor(ms)));
|
|
34
|
-
const runtime = _chunkWQ5QNU5Rcjs.unsafeGetCurrentRuntime.call(void 0, );
|
|
35
|
-
const fiber = runtime.fork(effect);
|
|
36
|
-
fiber.join((exit) => {
|
|
37
|
-
if (done) return;
|
|
38
|
-
done = true;
|
|
39
|
-
clearTimeout(timerId);
|
|
40
|
-
cb(exit);
|
|
41
|
-
});
|
|
42
|
-
return () => {
|
|
43
|
-
if (done) return;
|
|
44
|
-
done = true;
|
|
45
|
-
clearTimeout(timerId);
|
|
46
|
-
fiber.interrupt();
|
|
47
|
-
};
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
function retry(effect, policy) {
|
|
51
|
-
const shouldRetry = _nullishCoalesce(policy.shouldRetry, () => ( (() => true)));
|
|
52
|
-
const jitter = _nullishCoalesce(policy.jitter, () => ( "full"));
|
|
53
|
-
const maxElapsedMs = policy.maxElapsedMs;
|
|
54
|
-
const computeDelay = (attempt) => {
|
|
55
|
-
const exp = policy.baseDelayMs * Math.pow(2, attempt);
|
|
56
|
-
const capped = Math.min(exp, policy.maxDelayMs);
|
|
57
|
-
if (jitter === "none") return capped;
|
|
58
|
-
return Math.floor(Math.random() * capped);
|
|
59
|
-
};
|
|
60
|
-
const loop = (attempt, startedAt) => _chunkDJQ7OMMBcjs.asyncFold.call(void 0,
|
|
61
|
-
effect,
|
|
62
|
-
(error) => {
|
|
63
|
-
if (attempt >= policy.maxRetries) return _chunkDJQ7OMMBcjs.asyncFail.call(void 0, error);
|
|
64
|
-
if (!shouldRetry(error, attempt)) return _chunkDJQ7OMMBcjs.asyncFail.call(void 0, error);
|
|
65
|
-
if (maxElapsedMs !== void 0) {
|
|
66
|
-
const elapsed = performance.now() - startedAt;
|
|
67
|
-
if (elapsed >= maxElapsedMs) return _chunkDJQ7OMMBcjs.asyncFail.call(void 0, error);
|
|
68
|
-
}
|
|
69
|
-
const delay = computeDelay(attempt);
|
|
70
|
-
return _chunkDJQ7OMMBcjs.asyncFlatMap.call(void 0, sleep(delay), () => loop(attempt + 1, startedAt));
|
|
71
|
-
},
|
|
72
|
-
(value) => _chunkDJQ7OMMBcjs.asyncSucceed.call(void 0, value)
|
|
73
|
-
);
|
|
74
|
-
return _chunkDJQ7OMMBcjs.asyncFlatMap.call(void 0,
|
|
75
|
-
{ _tag: "Sync", thunk: () => performance.now() },
|
|
76
|
-
(startedAt) => loop(0, startedAt)
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
function retryN(effect, n) {
|
|
80
|
-
return retry(effect, {
|
|
81
|
-
maxRetries: n,
|
|
82
|
-
baseDelayMs: 0,
|
|
83
|
-
maxDelayMs: 0,
|
|
84
|
-
jitter: "none"
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
function retryWithBackoff(effect, opts = {}) {
|
|
88
|
-
return retry(effect, {
|
|
89
|
-
maxRetries: _nullishCoalesce(opts.maxRetries, () => ( 3)),
|
|
90
|
-
baseDelayMs: _nullishCoalesce(opts.baseDelayMs, () => ( 100)),
|
|
91
|
-
maxDelayMs: _nullishCoalesce(opts.maxDelayMs, () => ( 1e4)),
|
|
92
|
-
maxElapsedMs: opts.maxElapsedMs,
|
|
93
|
-
shouldRetry: opts.shouldRetry,
|
|
94
|
-
jitter: "full"
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
exports.sleep = sleep; exports.timeout = timeout; exports.retry = retry; exports.retryN = retryN; exports.retryWithBackoff = retryWithBackoff;
|
package/dist/chunk-7V4KY4RL.mjs
DELETED
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
unsafeGetCurrentRuntime
|
|
3
|
-
} from "./chunk-G6IQOE4P.mjs";
|
|
4
|
-
import {
|
|
5
|
-
asyncEffect,
|
|
6
|
-
asyncFail,
|
|
7
|
-
asyncFlatMap,
|
|
8
|
-
asyncFold,
|
|
9
|
-
asyncSucceed
|
|
10
|
-
} from "./chunk-NOYZIMUJ.mjs";
|
|
11
|
-
|
|
12
|
-
// src/core/runtime/combinators.ts
|
|
13
|
-
function sleep(ms) {
|
|
14
|
-
return asyncEffect((_env, cb) => {
|
|
15
|
-
const delay = Math.max(0, Math.floor(ms));
|
|
16
|
-
const id = setTimeout(() => cb({ _tag: "Success", value: void 0 }), delay);
|
|
17
|
-
return () => clearTimeout(id);
|
|
18
|
-
});
|
|
19
|
-
}
|
|
20
|
-
function timeout(effect, ms) {
|
|
21
|
-
return asyncEffect((env, cb) => {
|
|
22
|
-
let done = false;
|
|
23
|
-
let timerId;
|
|
24
|
-
let effectRunning = true;
|
|
25
|
-
timerId = setTimeout(() => {
|
|
26
|
-
if (done) return;
|
|
27
|
-
done = true;
|
|
28
|
-
effectRunning = false;
|
|
29
|
-
cb({
|
|
30
|
-
_tag: "Failure",
|
|
31
|
-
cause: { _tag: "Fail", error: { _tag: "TimeoutError", ms } }
|
|
32
|
-
});
|
|
33
|
-
}, Math.max(0, Math.floor(ms)));
|
|
34
|
-
const runtime = unsafeGetCurrentRuntime();
|
|
35
|
-
const fiber = runtime.fork(effect);
|
|
36
|
-
fiber.join((exit) => {
|
|
37
|
-
if (done) return;
|
|
38
|
-
done = true;
|
|
39
|
-
clearTimeout(timerId);
|
|
40
|
-
cb(exit);
|
|
41
|
-
});
|
|
42
|
-
return () => {
|
|
43
|
-
if (done) return;
|
|
44
|
-
done = true;
|
|
45
|
-
clearTimeout(timerId);
|
|
46
|
-
fiber.interrupt();
|
|
47
|
-
};
|
|
48
|
-
});
|
|
49
|
-
}
|
|
50
|
-
function retry(effect, policy) {
|
|
51
|
-
const shouldRetry = policy.shouldRetry ?? (() => true);
|
|
52
|
-
const jitter = policy.jitter ?? "full";
|
|
53
|
-
const maxElapsedMs = policy.maxElapsedMs;
|
|
54
|
-
const computeDelay = (attempt) => {
|
|
55
|
-
const exp = policy.baseDelayMs * Math.pow(2, attempt);
|
|
56
|
-
const capped = Math.min(exp, policy.maxDelayMs);
|
|
57
|
-
if (jitter === "none") return capped;
|
|
58
|
-
return Math.floor(Math.random() * capped);
|
|
59
|
-
};
|
|
60
|
-
const loop = (attempt, startedAt) => asyncFold(
|
|
61
|
-
effect,
|
|
62
|
-
(error) => {
|
|
63
|
-
if (attempt >= policy.maxRetries) return asyncFail(error);
|
|
64
|
-
if (!shouldRetry(error, attempt)) return asyncFail(error);
|
|
65
|
-
if (maxElapsedMs !== void 0) {
|
|
66
|
-
const elapsed = performance.now() - startedAt;
|
|
67
|
-
if (elapsed >= maxElapsedMs) return asyncFail(error);
|
|
68
|
-
}
|
|
69
|
-
const delay = computeDelay(attempt);
|
|
70
|
-
return asyncFlatMap(sleep(delay), () => loop(attempt + 1, startedAt));
|
|
71
|
-
},
|
|
72
|
-
(value) => asyncSucceed(value)
|
|
73
|
-
);
|
|
74
|
-
return asyncFlatMap(
|
|
75
|
-
{ _tag: "Sync", thunk: () => performance.now() },
|
|
76
|
-
(startedAt) => loop(0, startedAt)
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
function retryN(effect, n) {
|
|
80
|
-
return retry(effect, {
|
|
81
|
-
maxRetries: n,
|
|
82
|
-
baseDelayMs: 0,
|
|
83
|
-
maxDelayMs: 0,
|
|
84
|
-
jitter: "none"
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
function retryWithBackoff(effect, opts = {}) {
|
|
88
|
-
return retry(effect, {
|
|
89
|
-
maxRetries: opts.maxRetries ?? 3,
|
|
90
|
-
baseDelayMs: opts.baseDelayMs ?? 100,
|
|
91
|
-
maxDelayMs: opts.maxDelayMs ?? 1e4,
|
|
92
|
-
maxElapsedMs: opts.maxElapsedMs,
|
|
93
|
-
shouldRetry: opts.shouldRetry,
|
|
94
|
-
jitter: "full"
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
export {
|
|
99
|
-
sleep,
|
|
100
|
-
timeout,
|
|
101
|
-
retry,
|
|
102
|
-
retryN,
|
|
103
|
-
retryWithBackoff
|
|
104
|
-
};
|