Composable, testable, and observable pipelines for TypeScript.
Build robust flows from small, reusable actors. Inject state, record every step, and assert with confidence.
@cond8/core is a TypeScript framework for building and testing complex, stateful workflows:
- Actors: Pure functions that mutate or observe state.
- Directors: Compose actors into pipelines, manage lifecycle, and orchestrate data flow.
- Lifecycle hooks: Observe and extend every step, from entry to exit and error.
- In-memory recording: Capture all events for debugging, auditing, and test assertions.
- Test-first: Easily inject mocks and assertions for both unit and integration testing.
It's ideal for:
- Data processing pipelines
- Business logic engines
- Testable state machines
- Highly-observable, auditable flows
import { createRole, createDirector, CoreMetaHooks } from '@cond8/core';
// Actor: increments a counter in the conduit
const increment = createRole(c8 =>
c8.var('count', (c8.var('count', 0) ?? 0) + 1),
);
// Director: chains two increments
const pipeline = createDirector('counterDemo')(increment, increment)
.init(input => ({ conduit: input, recorder: undefined }))
.fin(readonly => readonly.var('count'));
// Run with test metadata
const result = await pipeline.test(
CoreMetaHooks.Director.TestInput({}),
CoreMetaHooks.Director.TestOutput(val => {
if (val !== 2) throw new Error('Expected 2!');
}),
);
console.log(result); // 2+-------------------+ +-------------------+ +-------------------+
| Actor (Role) | ---> | Actor (Role) | ---> | Actor (Role) |
+-------------------+ +-------------------+ +-------------------+
| | |
+-----------+---------------+---------------+-----------+
|
+-----------+
| Director |
+-----------+
|
+------------------+
| Recorder/Log |
+------------------+
- Actors: Stateless/pure or stateful functions, can be tested in isolation.
- Director: Orchestrates actors, manages lifecycle, and exposes
.test(). - Recorder: Captures every event, state diff, and error for inspection.
- Composable: Build flows by chaining simple, focused actors.
- Testable: Inject mocks and assertions directly into the pipeline.
- Observable: Every step, error, and state change is recorded.
- Extensible: Add custom hooks to observe or modify lifecycle events.
npm install @cond8/core
# or
pnpm add @cond8/core
yarn add @cond8/coreActors are functions that operate on a conduit (your state object). Use createRole to wrap them for metadata and testing:
const double = createRole(c8 => c8.var('count', (c8.var('count', 0) ?? 0) * 2));Directors chain actors, manage lifecycle, and expose a callable API:
const director = createDirector('mathFlow')(increment, double)
.init(input => ({ conduit: input, recorder: undefined }))
.fin(readonly => readonly.var('count'));
const result = await director({ count: 1 }); // 4Inject test data and assertions using CoreMetaHooks:
await director.test(
CoreMetaHooks.Director.TestInput({ count: 2 }),
CoreMetaHooks.Director.TestOutput(val => {
if (val !== 6) throw new Error('Expected 6!');
}),
);Observe or extend every step by implementing LifecycleBlueprint or using the built-in FullLifecycleBlueprint.
import { FullLifecycleBlueprint } from '@cond8/core';
class LoggingHook extends FullLifecycleBlueprint {
onEnter(payload) {
console.log('Entering:', payload);
}
onExit(payload) {
console.log('Exiting:', payload);
}
}
// Attach to your actor or director via metadata- Custom Blueprints: Extend
CoreBlueprintorCoreRedprintfor your own state logic. - Metadata Hooks: Create new
MetaHookclasses for richer test and runtime metadata. - Recorder: Write your own recorder to persist logs, send telemetry, or integrate with external systems.
src/
ββ CoreDomain/ # Blueprints, Redprints, and base services
ββ CoreInfra/ # Actor, role, and director factories
ββ Lifecycle/ # Event hooks and payload collector
ββ Metadata/ # Test metadata and filtering
ββ Recorder/ # Recording, proxying, and error handling
ββ utils/ # Helpers
pnpm buildβ Compile TypeScriptpnpm devβ Watch & rebuildpnpm lintβ ESLintpnpm formatβ Prettierpnpm testβ Vitest
We welcome issues, suggestions, and PRs! Please open an issue to discuss your idea or bug before submitting major changes.
Apache 2.0 β Β© cond8 contributors
-
createRole(actorScript: (c8: T) => Promise | T)
- Returns a factory for
StagedActorwith built-in metadata injection and.test().
- Returns a factory for
-
createDirector(name: string)
- Builds a
Directorto chain actors:- supply actors via
(...)or.appendActors/.prependActors - define an input mapper (
.init) - define an output mapper (
.fin)
- supply actors via
- Result is a callable
Executablewith.test()and lifecycle recording.
- Builds a
-
ConduitUtils (on any
c8.utils):.var(key, value?)β get/set per-actor local state.readonlyβ snapshot all blueprint states.diff()/.stringify()β inspect state changes.close(...)β terminate and wrap errors inC8Error.handleEvent(...)β dispatch lifecycle hooks
Vacuum collects a LifecyclePayload object at each step, and ConduitUtils.handleEvent calls any active LifecycleBlueprint implementations. The default FullLifecycleBlueprint simply logs events to the recorder.
Inject test metadata via CoreMetaHooks:
Actor.TestInput(mockC8)/Actor.TestOutput(assertFn)Director.TestInput(input)/Director.TestOutput(assertFn)
Use these in .test() runs to mock inputs or assert outputs without external test runners.
pnpm buildβ compile TypeScriptpnpm devβ watch & rebuildpnpm lintβ ESLintpnpm formatβ Prettierpnpm testβ Vitest
src/
ββ CoreDomain/
β β Blueprints/ // abstract state & lifecycle definitions
β β Redprints/ // CoreRedprint & ConduitUtils
β β Services/ // KV service
ββ CoreInfra/
β β create-actor.ts // actor wrapping + tests
β β create-role.ts // role factories
β β create-director.ts // pipeline orchestration
ββ Lifecycle/
β β LifecycleEventHooks.ts // default hook impls
β β Vacuum.ts // payload collector
ββ Metadata/ // test hooks & filters
ββ Recorder/ // recording, proxy handlers, errors
ββ utils/ // small helpers
Contributions welcome! Please open issues or PRs.
Apache License Version 2.0, January 2004 http://www.apache.org/licenses/