Add @evryg/effect-bdd: a runner-agnostic, inspectable BDD DSL for Effect#107
Open
jbmusso wants to merge 11 commits into
Open
Add @evryg/effect-bdd: a runner-agnostic, inspectable BDD DSL for Effect#107jbmusso wants to merge 11 commits into
jbmusso wants to merge 11 commits into
Conversation
jbmusso
force-pushed
the
effect-bdd-dsl
branch
3 times, most recently
from
June 23, 2026 07:29
ce9c933 to
15aa147
Compare
Mirror the packages/neo4j layout (tsconfig set, vitest/docgen config) and wire the package into the root tsconfig references, path aliases and the vitest shared alias map. Placeholder src/index.ts compiles cleanly via tsc -b.
Add Given/When/Assertion combinator values (Step.ts) and the reified Scenario term with its phase-typed builder (Scenario.ts): given/and accumulate a named-key context and enforce combinator-declared dependencies, when introduces the action's A/E, and then/thenFails/thenDies select the outcome and feed the correct payload to each assertion. Authoring yields data, not an Effect. Unit tests cover the reified shape from both the sugar and combinator forms; Scenario.test-d.ts locks the four compile-time guarantees (phase ordering, context accumulation, dependency enforcement, outcome-correct payloads).
run folds the givens into a context, performs the action, classifies its Exit (success / typed failure / defect) and checks each assertion against the chosen outcome. Mismatches, failed preconditions and broken assertions surface as a typed ScenarioError — assertion throws/failed Effects are captured via Effect.exit, never a runner's expect, so a scenario runs under any runner. Unit tests cover success, thenFails, thenDies, assertion failure, outcome mismatch and service threading; Run.test-d.ts asserts run demands exactly the accumulated R.
Treat a Scenario as inspectable data: steps lists its steps with Gherkin keywords, tags returns its tags, toGherkin renders the scenario as Gherkin text, and toDocument projects the describable skeleton onto a serializable ScenarioDocument schema (for .feature export). None of these execute the scenario. Unit tests cover keywords, recorded outcomes, rendering and a Schema round-trip.
feature groups scenarios under a name; filterByTags keeps the scenarios carrying any of the given tags (order-preserving), and selectByTags narrows a feature. Because a Scenario is a value, a suite is just data to filter and reorder. Unit tests cover grouping, suite filtering and feature narrowing.
Add README, a changeset (minor), and two PascalCase examples (CartDsl, CounterDsl) showing higher-level domain vocabularies built from the combinators — constrained sub-builders and specialised type safety (context-requiring combinators cannot be chained before their precondition). The same is exercised as tests: DomainDsl unit tests run a domain DSL through run, DomainDsl.test-d locks the specialised safety, and Bdd.node.unit demonstrates one scenario interpreted three ways plus a guardrail asserting the core imports no test runner. examples are type-checked via tsconfig.examples.json.
Move the five generic DSL modules and their tests into src/core/ (mirroring the cypher-codegen subfolder convention), leaving the barrel at src/index.ts pointing to ./core/*. The former Bdd test becomes core/Interpreting (one scenario, many interpreters). Public API and path alias unchanged.
A dispatcher folds a tagged command value into a core When via a per-tag handler map, deriving the step description from the command. The command is data, so the same command type can be driven by multiple dispatchers (e.g. real vs. dry-run) — the re-interpretability an initial encoding buys. Unit tests cover dispatch, derived descriptions and re-interpretation; test-d locks exhaustive handlers and the produced When type.
probe wraps a service in a Layer.mock backed by an eager call-log: the action runs against the mock, and the recorded calls are read back through .calls (typically in an assertion). Unimplemented members fail loudly. Unit tests cover recording and the loud-failure path.
makeHarness bundles initial preconditions + probes + a dispatch function into a domain-specialized builder: reducer givens fold over the preconditions, when takes a bare command, then/thenFails/thenDies receive the probes' recorded calls as an injected observation object, and run auto-provides the merged probe layers (discharging their services from R) and resets each probe's call-log first, so a harness can be reused across scenarios. It is a pure composition over the core — the core term and run are untouched, so scenarios stay runner-agnostic and inspectable. Probe gains reset; unit + test-d cover injected observations, the failure path, service discharge and no-accumulation across runs.
Move the combinator-based examples to examples/core/{Cart,Counter}.ts and add
examples/harness/{Cart,Counter}.ts expressing the same two domains via the
preset harness (reified commands, a probed port, injected observations,
auto-provided layers). Document both modes in the README and extend the
changeset to cover the harness layer.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Adds
@evryg/effect-bdd— a runner-agnostic, inspectable Behaviour-Driven-Development DSL for Effect.A scenario is reified data built with a type-safe, phase-typed
Given / When / Thenbuilder, so the same value can be:ScenarioError, never via a runner'sexpect;Two layers
core/— the generic DSLStep(Given/When/Assertion combinator values),Scenario(term + phase-typed builder),Run(theruninterpreter +ScenarioError),Gherkin(render /steps/tags/toDocument),Feature(group +filterByTags).then/thenFails/thenDies) the correct payload type.harness/— an optional layer, a pure composition over the (untouched) coreCommand— reified, re-interpretable actions viaSchema.TaggedUnion+ adispatcher(the tag is managed by Effect; no_tagwritten by hand).Probe— service spies (Layer.mock+ a call-log) that capture indirect outputs.makeHarness— a per-domain preset:whentakes a bare command,thenreceives the probes' recorded calls as an injected observation object, andrunauto-provides the probe layers.Examples
examples/core/{Cart,Counter}.ts(combinator mode) andexamples/harness/{Cart,Counter}.ts(preset mode) — the same two domains expressed both ways.Verification
pnpm check(typecheck) — cleanpnpm test— 283 tests pass (incl.*.test-d.tstype-level acceptance tests)pnpm circular,pnpm lint— cleanpnpm docgen— succeeds@evryg/effect-bdd: minor)