Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
5891ccf
Initial plan
Copilot Mar 11, 2026
537b9eb
Migrate fallback-bug.test.ts from forEachEngine to regressionTest
Copilot Mar 11, 2026
283d337
Migrate chained.test.ts from forEachEngine to regressionTest
Copilot Mar 11, 2026
c7d7a8d
Migrate string-interpolation.test.ts and interpolation-universal.test…
Copilot Mar 11, 2026
001c3d7
Migrate prototype-pollution.test.ts from forEachEngine
Copilot Mar 11, 2026
24670aa
Migrate ternary.test.ts from forEachEngine to regressionTest (partial…
Copilot Mar 11, 2026
d198728
Fix ternary refFallback trace assertion: runtime lazy vs compiler eager
Copilot Mar 11, 2026
806fbbe
refactor: migrate expressions.test.ts from forEachEngine to regressio…
Copilot Mar 11, 2026
d4ef9f6
refactor: migrate force-wire tests to regressionTest harness
Copilot Mar 11, 2026
a6711cc
Move remaining forEachEngine tests to legacy/ with README.md of actio…
Copilot Mar 11, 2026
a6cfc0b
Migrate infinite-loop-protection.test.ts to regressionTest (partial)
Copilot Mar 11, 2026
19f6164
Fix infinite-loop-protection.test.ts: fix traversal coverage and grap…
Copilot Mar 11, 2026
15073cc
Fix serializer: support expressions, ternary, coalesce, string interp…
Copilot Mar 11, 2026
f9aa6b0
test: migrate tool-self-wires-runtime to regressionTest harness
Copilot Mar 11, 2026
75c99af
feat: migrate native-batching tests to regressionTest harness
Copilot Mar 11, 2026
5736138
Update legacy README with migration patterns and remove migrated files
Copilot Mar 11, 2026
3baa0a7
New tsc setup
aarne Mar 12, 2026
80fb44b
Fix build
aarne Mar 12, 2026
01b9a79
feat: add bridge-types dependency to bridge-compiler and update pnpm-…
aarne Mar 12, 2026
c7824d5
feat: refactor multitool functions for improved error handling and cl…
aarne Mar 12, 2026
eaa6158
Fix graphql control flog bug
aarne Mar 12, 2026
72b69ad
Control flow tests are migrated
aarne Mar 12, 2026
b1c1c26
Test structure
aarne Mar 12, 2026
e90919e
feat: more compiler coverage
aarne Mar 12, 2026
1ca2fc6
feat: enhance error handling and add new regression tests for express…
aarne Mar 12, 2026
8d20f28
fix: fuzzer
aarne Mar 12, 2026
c01557f
Tests
aarne Mar 12, 2026
0b09127
Some progress
aarne Mar 12, 2026
3ade997
Move back to legacy
aarne Mar 12, 2026
eb89021
fix tests
aarne Mar 12, 2026
f2434bb
fix: update tools type to Record<string, unknown> in buildAotFn and c…
aarne Mar 12, 2026
9e85988
Hallukad jalle
aarne Mar 12, 2026
f4b31bf
Test stability
aarne Mar 12, 2026
8c92529
Broke a bunch of things
aarne Mar 12, 2026
8b14966
Fixed some stuff
aarne Mar 12, 2026
164b690
Broke some stuff/ fixed some more stuff
aarne Mar 12, 2026
5b2f5ad
Fixed more stuff
aarne Mar 12, 2026
c3efbd8
Broke some stuff again
aarne Mar 12, 2026
9797e64
Did not really mnage to fix all
aarne Mar 13, 2026
01047cf
Half fixes
aarne Mar 13, 2026
479fe5a
Now to graphql
aarne Mar 13, 2026
ce9f966
graphql tessts
aarne Mar 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ jobs:
run: pnpm install

- name: Run benchmarks
run: cd packages/bridge && CI=true node --experimental-transform-types --conditions source bench/engine.bench.ts > bench-results.json 2>/dev/null
run: cd packages/bridge && CI=true node --experimental-transform-types bench/engine.bench.ts > bench-results.json 2>/dev/null

- name: Upload benchmark results
uses: actions/upload-artifact@v4
Expand Down Expand Up @@ -75,7 +75,7 @@ jobs:
run: pnpm install

- name: Run benchmarks
run: cd packages/bridge && CI=true node --experimental-transform-types --conditions source bench/engine.bench.ts > bench-results.json 2>/dev/null
run: cd packages/bridge && CI=true node --experimental-transform-types bench/engine.bench.ts > bench-results.json 2>/dev/null

- uses: bencherdev/bencher@main

Expand Down
4 changes: 2 additions & 2 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ playground/ Browser playground (Vite + React)

**Run a single test file:**
```bash
node --experimental-transform-types --conditions source --test test/<filename>.test.ts
node --experimental-transform-types --test test/<filename>.test.ts
```

Tests are **co-located with each package**. The main test suites:
Expand All @@ -77,7 +77,7 @@ Tests are **co-located with each package**. The main test suites:

- **ESM** (`"type": "module"`) with `.ts` import extensions (handled by `rewriteRelativeImportExtensions`)
- **Strict mode** — `noUnusedLocals`, `noUnusedParameters`, `noImplicitReturns`, `noFallthroughCasesInSwitch`
- **Dev running:** `--experimental-transform-types --conditions source`
- **Dev running:** `--experimental-transform-types`
- **Path mappings:** `tsconfig.base.json` maps `@stackables/*` for cross-package imports

## Deep-dive docs
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,3 +119,11 @@ const schema = bridgeTransform(createSchema({ typeDefs }), instructions, {
```

**[Read the Tools & Extensions Guide](https://bridge.sdk42.com/advanced/custom-tools/)**

## Testing Prompt

The reason we write tests is to catch bugs so we can fix them — not to document broken behavior and ship it.

We never hide problems or avoid broken scenarios to make tests pass.

It is always better to not ship and have broken tests than to break our users trust.
6 changes: 3 additions & 3 deletions docs/fuzz-testing.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,9 +108,9 @@ When a fuzz run finds a new issue:
pnpm test

# Single fuzz file
node --experimental-transform-types --conditions source --test packages/bridge-compiler/test/fuzz-runtime-parity.test.ts
node --experimental-transform-types --conditions source --test packages/bridge/test/fuzz-parser.test.ts
node --experimental-transform-types --conditions source --test packages/bridge-stdlib/test/fuzz-stdlib.test.ts
node --experimental-transform-types --test packages/bridge-compiler/test/fuzz-runtime-parity.test.ts
node --experimental-transform-types --test packages/bridge/test/fuzz-parser.test.ts
node --experimental-transform-types --test packages/bridge-stdlib/test/fuzz-stdlib.test.ts

# Reproduce a specific failing seed
# Add { seed: -1234567, path: "0", endOnFailure: true } to fc.assert options
Expand Down
2 changes: 1 addition & 1 deletion docs/profiling.md
Original file line number Diff line number Diff line change
Expand Up @@ -470,7 +470,7 @@ Use the focused profiling target instead:
```bash
# Runs a single scenario in a tight loop — cleaner profiles
BRIDGE_PROFILE_FILTER="flat array 1000" BRIDGE_PROFILE_ITERATIONS=10000 \
node --experimental-transform-types --conditions source \
node --experimental-transform-types \
--cpu-prof --cpu-prof-dir profiles --cpu-prof-interval 50 \
scripts/profile-target.mjs
```
Expand Down
209 changes: 209 additions & 0 deletions docs/test-migration-playbook.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,209 @@
# Test Migration Playbook: Legacy → regressionTest

Migrate `packages/bridge/test/legacy/*.test.ts` to the `regressionTest` framework.

## Prerequisites

- Read `packages/bridge/test/utils/regression.ts` (the framework — DO NOT EDIT)
- Read `packages/bridge/test/utils/bridge-tools.ts` (test multitools)
- Study `packages/bridge/test/coalesce-cost.test.ts` as the gold-standard example

## Step-by-step process

### 1. Categorise every test in the legacy file

Read the file and sort each test into one of these buckets:

| Bucket | Action |
| ------------------------------------------------------------------- | -------------------------------------------------------------------------------------------- |
| **Parser-only** (parses AST, checks wire structure) | DELETE — regressionTest's `parse → serialise → parse` covers this automatically |
| **Serializer roundtrip** (parse → serialize → parse) | DELETE — regressionTest does this automatically |
| **Runtime execution** (runs bridge, asserts data/errors) | MIGRATE to `regressionTest` scenarios |
| **Non-runtime tests** (class constructors, pure unit tests) | MOVE to the corresponding package test dir (e.g. `bridge-core/test/`, `bridge-parser/test/`) |
| **Tests requiring custom execution** (AbortSignal, custom contexts) | Keep using `forEachEngine` in the new file |

### 2. Design bridges for regressionTest

Group related runtime-execution tests into **logical regressionTest blocks**. Each block has:

```typescript
regressionTest("descriptive name", {
bridge: `
version 1.5
bridge Operation.field {
with test.multitool as a
with input as i
with output as o
// ... wires
}
`,
tools, // import { tools } from "./utils/bridge-tools.ts"
scenarios: {
"Operation.field": {
"scenario name": { input: {...}, assertData: {...}, assertTraces: N },
},
},
});
```

**Design rules:**

- One regressionTest can have **multiple bridges** (multiple operations in scenarios)
- Group by **feature/behavior** (e.g. "throw control flow", "continue/break in arrays")
- Each bridge needs enough scenarios to achieve **traversal coverage** (all non-error paths hit)
- Keep bridge definitions minimal — test one concept per wire

### 3. Use test.multitool everywhere possible

The multitool (`with test.multitool as a`) is a passthrough: input → output (minus `_`-prefixed keys).

**Capabilities:**

- `_error`: `input: { a: { _error: "boom" } }` → tool throws `Error("boom")`
- `_delay`: `input: { a: { _delay: 100, name: "A" } }` → delays 100ms, returns `{ name: "A" }`
- All other `_` keys are stripped from output
- Correctly handles nested objects and arrays

**Wiring pattern:**

```
a <- i.a // sends i.a as input to tool, tool returns cleaned copy
o.x <- a.y // reads .y from tool output
```

**Only use custom tool definitions when:**

- You need a tool that transforms data (not passthrough)
- You need AbortSignal handling on the tool side
- You need `ctx.signal` inspection

### 4. Write scenarios

Each scenario needs:

| Field | Required | Description |
| ---------------- | -------- | ----------------------------------------------------------------------- |
| `input` | Yes | Input object passed to bridge |
| `assertTraces` | Yes | Number of tool calls (or function for custom check) |
| `assertData` | No | Expected output data (object or function) |
| `assertError` | No | Expected error (regex or function) — mutually exclusive with assertData |
| `fields` | No | Restrict which output fields are resolved |
| `context` | No | Context values (for `with context as ctx`) |
| `tools` | No | Per-scenario tool overrides |
| `allowDowngrade` | No | Set `true` if compiler can't handle this bridge feature |
| `assertGraphql` | No | GraphQL-specific expectations (object or function) |
| `assertLogs` | No | Log assertions |

**assertData shorthand:** For simple cases, use object literal:

```typescript
assertData: { name: "Alice", age: 30 }
```

**assertError with regex:** Matches against `${error.name} ${error.message}`:

```typescript
assertError: /BridgeRuntimeError/; // matches error name
assertError: /name is required/; // matches error message
assertError: /BridgePanicError.*fatal/; // matches both
```

**assertError with function** (for instanceof checks):

```typescript
assertError: (err: any) => {
assert.ok(err instanceof BridgePanicError);
assert.equal(err.message, "fatal");
};
```

**fields for isolating wires:** When one wire throws but others don't, use `fields` to test them separately:

```typescript
"error on fieldA only": {
input: { ... },
fields: ["fieldA"], // only resolve this field
assertError: /message/,
assertTraces: 0,
},
```

### 5. Handle traversal coverage

The framework automatically checks that all non-error traversal paths are covered. Common uncovered paths:

- **empty-array**: Add a scenario with an empty array: `input: { a: { items: [] } }`
- **Fallback paths**: Add a scenario where each fallback fires
- **Short-circuit paths**: Add scenarios for each branch of ||/?? chains

If traversal coverage fails, the error message tells you exactly which paths are missing.

### 6. Handle compiler downgrade

The compiled engine doesn't support all features. When the compiler downgrades, add `allowDowngrade: true` to the scenario. Common triggers:

- `?.` (safe execution modifier) without `catch`
- Some complex expressions
- Certain nested array patterns

**Important:** `allowDowngrade` applies per-scenario, but the bridge is shared. If ANY wire in the bridge triggers downgrade, ALL scenarios need `allowDowngrade: true`.

### 7. Handle errors in GraphQL

as graphql has partial errors then we need to assert it separately

```typescript
assertGraphql: {
fieldA: /error message/i, // expect GraphQL error for this field
fieldB: "fallback-value", // expect this value
}
```

### 8. Move non-runtime tests

Tests that don't invoke the bridge execution engine belong in the corresponding package:

| Test type | Target |
| ------------------------ | -------------------------------------------------- |
| Error class constructors | `packages/bridge-core/test/execution-tree.test.ts` |
| Parser AST structure | `packages/bridge-parser/test/` |
| Serializer output format | `packages/bridge-parser/test/` |
| Type definitions | `packages/bridge-types/test/` |

### 9. Final verification

```bash
pnpm build # 0 type errors
pnpm lint # 0 lint errors
pnpm test # 0 failures
```

Run the specific test file first for fast iteration:

```bash
node --experimental-transform-types --test packages/bridge/test/<new-file>.test.ts
```

## Migration checklist template

For each legacy test file:

- [ ] Read and categorise all tests
- [ ] Delete parser-only and roundtrip tests (covered by regressionTest)
- [ ] Design bridges using test.multitool
- [ ] Write scenarios with correct assertions
- [ ] Ensure traversal coverage (add empty-array, fallback scenarios)
- [ ] Add `allowDowngrade: true` where compiler downgrades
- [ ] Handle GraphQL replay bugs with `assertGraphql: () => {}`
- [ ] Move non-runtime tests to corresponding package
- [ ] Keep tests needing custom execution (AbortSignal) using `forEachEngine`
- [ ] Verify: `pnpm build && pnpm lint && pnpm test`
- [ ] Don't delete the legacy file until confirmation

## Files remaining to migrate

```
packages/bridge/test/legacy/ # check for remaining legacy tests
packages/bridge/test/expressions.test.ts # if still using forEachEngine
packages/bridge/test/infinite-loop-protection.test.ts # if still using forEachEngine
```
4 changes: 2 additions & 2 deletions examples/builtin-tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"private": true,
"type": "module",
"scripts": {
"start": "node --experimental-transform-types --conditions source server.ts",
"e2e": "node --experimental-transform-types --conditions source --test e2e.test.ts"
"start": "node --experimental-transform-types server.ts",
"e2e": "node --experimental-transform-types --test e2e.test.ts"
},
"dependencies": {
"@stackables/bridge": "workspace:*",
Expand Down
4 changes: 2 additions & 2 deletions examples/composed-gateway/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"private": true,
"type": "module",
"scripts": {
"start": "node --experimental-transform-types --conditions source server.ts",
"e2e": "node --experimental-transform-types --conditions source --test e2e.test.ts"
"start": "node --experimental-transform-types server.ts",
"e2e": "node --experimental-transform-types --test e2e.test.ts"
},
"dependencies": {
"@stackables/bridge": "workspace:*",
Expand Down
4 changes: 2 additions & 2 deletions examples/travel-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"private": true,
"type": "module",
"scripts": {
"start": "node --experimental-transform-types --conditions source server.ts",
"e2e": "node --experimental-transform-types --conditions source --test e2e.test.ts"
"start": "node --experimental-transform-types server.ts",
"e2e": "node --experimental-transform-types --test e2e.test.ts"
},
"dependencies": {
"@stackables/bridge": "workspace:*",
Expand Down
4 changes: 2 additions & 2 deletions examples/weather-api/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
"private": true,
"type": "module",
"scripts": {
"start": "node --experimental-transform-types --conditions source server.ts",
"e2e": "node --experimental-transform-types --conditions source --test e2e.test.ts"
"start": "node --experimental-transform-types server.ts",
"e2e": "node --experimental-transform-types --test e2e.test.ts"
},
"dependencies": {
"@stackables/bridge": "workspace:*",
Expand Down
2 changes: 1 addition & 1 deletion examples/without-graphql/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"scripts": {
"weather": "node --experimental-transform-types cli.ts weather.bridge '{\"city\":\"Berlin\"}'",
"sbb": "node --experimental-transform-types cli.ts sbb.bridge '{\"from\":\"Bern\",\"to\":\"Zürich\"}'",
"e2e": "node --experimental-transform-types --conditions source --test e2e.test.ts"
"e2e": "node --experimental-transform-types --test e2e.test.ts"
},
"dependencies": {
"@stackables/bridge": "workspace:*"
Expand Down
5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,15 @@
"profile:heap": "node scripts/profile-heap.mjs",
"profile:deopt": "node scripts/profile-deopt.mjs",
"profile:flamegraph": "node scripts/flamegraph.mjs",
"bench:compare": "node scripts/bench-compare.mjs"
"bench:compare": "node scripts/bench-compare.mjs",
"mutants": "npx stryker run"
},
"devDependencies": {
"@changesets/changelog-github": "^0.6.0",
"@changesets/cli": "^2.30.0",
"@eslint/js": "^10.0.1",
"@stryker-mutator/core": "^9.6.0",
"@stryker-mutator/typescript-checker": "^9.6.0",
"@tsconfig/node24": "^24.0.4",
"eslint": "^10.0.2",
"tinybench": "^6.0.0",
Expand Down
Loading
Loading