Package Exports
- @cnstra/core
Readme
@cnstra/core
Graph-routed, type-safe orchestration for reactive apps ā no global event bus.
š§ What is CNStra?
CNStra (Central Nervous System Orchestrator) models your app as a typed neuron graph.
You explicitly start a run with cns.stimulate(...); CNStra then performs a deterministic, hop-bounded traversal from collateral ā dendrite ā returned signal, step by step.
Zero dependencies: CNS has no third-party dependencies, making it suitable for any JavaScript/TypeScript environment - browsers, Node.js, serverless, edge functions, React Native, or embedded systems.
Not pub/sub: there are no ambient listeners or global
emit. Only the signal you return from a dendrite continues the traversal; returningnull/undefinedends that branch. Hop limits guard against cycles.
šļø Core Model
Neurons
Units of logic with clear DI and sharp boundaries:
- ID ā unique name
- Axon ā the neuron's output channels (its collaterals)
- Dendrites ā input receptors (typed reactions bound to specific collaterals)
Collaterals
Typed output channels that mint signals:
- ID ā string identifier (e.g.,
'user:created') - Payload ā the shape carried by the signal
createSignal(payload)ā{ collateral, payload }
Signals
The data structures that flow through the system:
- collateral ā reference to the collateral that created this signal
- payload ā the typed data being transmitted
š Quick Start
npm install @cnstra/coreimport { CNS, collateral, neuron } from '@cnstra/core';
// Define collaterals (communication channels)
const userCreated = collateral<{ id: string; name: string }>('user:created');
const userRegistered = collateral<{ userId: string; status: string }>('user:registered');
// Create a neuron
const userService = neuron('user-service', {
userRegistered
})
.dendrite({
collateral: userCreated,
response: (payload, axon) => {
const userData = payload;
// Process the user creation
console.log(`Processing user: ${userData.name}`);
// Return the signal that will be processed by CNS
return axon.userRegistered.createSignal({
userId: userData.id,
status: 'completed'
});
}
});
// Create the CNS system
const cns = new CNS([userService]);
// Stimulate the system
await cns.stimulate(userCreated.createSignal({
id: '123',
name: 'John Doe'
}));š API Reference
collateral<T>(id: string)
Creates a new collateral (communication channel).
const userEvent = collateral<{ userId: string }>('user:event');
const simpleEvent = collateral('simple:event'); // No payload typeneuron(id: string, axon: Axon)
Creates a new neuron with the specified axon (output channels).
const myNeuron = neuron('my-neuron', {
output: myCollateral
});neuron.dendrite(dendrite: Dendrite)
Adds a dendrite (input receptor) to a neuron. Returns the neuron for chaining.
myNeuron
.dendrite({
collateral: inputCollateral,
response: async (payload, axon, ctx) => {
// Process input and return output signal
// ctx parameter provides local context storage for this neuron
return axon.output.createSignal(result);
}
});CNS Class
The main orchestrator that manages signal flow between neurons.
Constructor
new CNS(neurons, options?)Parameters:
neurons: Array of neurons that process signalsoptions: Optional CNS configuration
stimulate() Method
cns.stimulate(signal, options?)Parameters:
signal: A signal created bycollateral.createSignal(payload)options: Optional stimulation configuration
Returns: Promise<void> that resolves when stimulation completes
Example:
await cns.stimulate(
userCreated.createSignal({ id: '123', name: 'John' })
);āļø Stimulation Options
maxNeuronHops?: number (default: 1000)
Prevents infinite loops by limiting signal traversal depth.
await cns.stimulate(signal, {
maxNeuronHops: 50 // Stop after 50 neuron hops
});onResponse?: (response) => void
Real-time callback for monitoring signal flow and completion.
await cns.stimulate(signal, {
onResponse: (response) => {
console.log(`Signal: ${response.outputSignal?.collateral.id}`);
console.log(`Hops: ${response.hops}`);
if (response.error) {
console.error('Processing failed:', response.error);
}
if (response.queueLength === 0) {
console.log('Stimulation completed');
}
}
});Response Object:
outputSignalā The signal being processed (if any)hopsā Number of neuron hops taken so farqueueLengthā Remaining signals in processing queue (0 = complete)errorā Any error that occurred during processingstimulationIdā Unique identifier for this stimulation
abortSignal?: AbortSignal
Gracefully stop stimulation using AbortController.
const controller = new AbortController();
cns.stimulate(signal, {
abortSignal: controller.signal
});
// Cancel after 5 seconds
setTimeout(() => controller.abort(), 5000);stimulationId?: string
Custom identifier for this stimulation cascade. Auto-generated if not provided.
await cns.stimulate(signal, {
stimulationId: 'user-action-' + Date.now()
});allowType?: (collateralId: string) => boolean
Filter which collateral types can be processed.
await cns.stimulate(signal, {
allowType: (type) => type.startsWith('user:') // Only process user-related signals
});concurrency?: number (default: unlimited)
Limit concurrent operations to prevent resource exhaustion.
await cns.stimulate(signal, {
concurrency: 10 // Max 10 operations at once
});ctx?: ICNSStimulationContextStore
Provide existing context store for recovery/retry scenarios.
await cns.stimulate(signal, {
ctx: savedContextStore // Restore previous state
});createContextStore?: () => ICNSStimulationContextStore
Factory for custom context store implementations.
await cns.stimulate(signal, {
createContextStore: () => new CustomContextStore()
});š Signal Flow Patterns
Basic Chain Processing
const input = collateral<{ value: number }>('input');
const middle = collateral<{ doubled: number }>('middle');
const output = collateral<{ result: string }>('output');
const step1 = neuron('step1', { middle }).dendrite({
collateral: input,
response: (payload, axon) => {
return axon.middle.createSignal({ doubled: payload.value * 2 });
}
});
const step2 = neuron('step2', { output }).dendrite({
collateral: middle,
response: (payload, axon) => {
return axon.output.createSignal({ result: `Final: ${payload.doubled}` });
}
});
const cns = new CNS([step1, step2]);
await cns.stimulate(input.createSignal({ value: 5 }));
// Flows: input(5) ā middle(10) ā output("Final: 10")Fan-out Processing
const trigger = collateral<{ data: string }>('trigger');
const branch1 = collateral<{ result: string }>('branch1');
const branch2 = collateral<{ result: string }>('branch2');
const processor1 = neuron('proc1', { branch1 }).dendrite({
collateral: trigger,
response: (payload, axon) => {
return axon.branch1.createSignal({ result: `A-${payload.data}` });
}
});
const processor2 = neuron('proc2', { branch2 }).dendrite({
collateral: trigger,
response: (payload, axon) => {
return axon.branch2.createSignal({ result: `B-${payload.data}` });
}
});
const cns = new CNS([processor1, processor2]);
await cns.stimulate(trigger.createSignal({ data: 'test' }));
// Flows: trigger("test") ā [branch1("A-test"), branch2("B-test")]Context-Aware Processing
import { withCtx } from '@cnstra/core';
const input = collateral<{ increment: number }>('input');
const output = collateral<{ count: number }>('output');
const counter = withCtx<{ total: number }>()
.neuron('counter', { output })
.dendrite({
collateral: input,
response: async (payload, axon, ctx) => {
const current = ctx.get()?.total || 0;
const newTotal = current + payload.increment;
ctx.set({ total: newTotal });
return axon.output.createSignal({ count: newTotal });
}
});
const cns = new CNS([counter]);
await cns.stimulate(input.createSignal({ increment: 5 })); // count: 5
await cns.stimulate(input.createSignal({ increment: 3 })); // count: 8 (separate context)š§ Memory & Performance
Memory-Efficient Design
- Zero dependencies: No third-party packages
- No error storage: Errors delivered via callbacks, not stored
- Streaming responses: Signal traces delivered via callbacks
- Context on-demand: Context stores created only when needed
- No global state: Clean slate between stimulations
Performance Characteristics
- Sync-first: Synchronous chains execute in single tick
- Minimal async overhead: Promises created only when needed
- Stack-safe: Handles deep chains without stack overflow
- Bounded execution:
maxNeuronHopsprevents runaway processing
Best Practices
- Keep context data minimal (IDs, counters, flags)
- Use synchronous responses when possible
- Set reasonable
maxNeuronHopslimits - Implement proper error handling in
onResponse
šÆ Common Use Cases
HTTP Request Processing
const httpRequest = collateral<{ method: string; url: string }>('http:request');
const requestValidated = collateral<{ method: string; url: string }>('request:validated');
const responseReady = collateral<{ status: number; body: any }>('response:ready');
const validator = neuron('validator', { requestValidated }).dendrite({
collateral: httpRequest,
response: (payload, axon) => {
if (!payload.url.startsWith('https://')) {
throw new Error('Only HTTPS URLs allowed');
}
return axon.requestValidated.createSignal(payload);
}
});
const handler = neuron('handler', { responseReady }).dendrite({
collateral: requestValidated,
response: async (payload, axon) => {
const response = await fetch(payload.url, { method: payload.method });
const body = await response.json();
return axon.responseReady.createSignal({ status: response.status, body });
}
});
const cns = new CNS([validator, handler]);Event Sourcing
const eventReceived = collateral<{ type: string; data: any }>('event:received');
const eventStored = collateral<{ eventId: string }>('event:stored');
const stateUpdated = collateral<{ aggregateId: string }>('state:updated');
const eventStore = neuron('event-store', { eventStored }).dendrite({
collateral: eventReceived,
response: async (payload, axon) => {
const eventId = await saveEvent(payload);
return axon.eventStored.createSignal({ eventId });
}
});
const stateManager = neuron('state-manager', { stateUpdated }).dendrite({
collateral: eventStored,
response: async (payload, axon) => {
const aggregateId = await updateState(payload.eventId);
return axon.stateUpdated.createSignal({ aggregateId });
}
});
const cns = new CNS([eventStore, stateManager]);šØ Error Handling
Errors are delivered immediately via onResponse callbacks:
await cns.stimulate(signal, {
onResponse: (response) => {
if (response.error) {
console.error(`Error in neuron processing:`, response.error);
// Log error details
console.error(`Signal: ${response.outputSignal?.collateral.id}`);
console.error(`Stimulation: ${response.stimulationId}`);
// Handle specific error types
if (response.error instanceof ValidationError) {
handleValidationError(response.error);
}
}
}
});Error Recovery with Context:
let savedContext: ICNSStimulationContextStore | undefined;
await cns.stimulate(signal, {
onResponse: (response) => {
if (response.error) {
// Save context for retry
savedContext = response.contextStore;
}
}
});
// Retry with preserved context
if (savedContext) {
await cns.stimulate(retrySignal, {
ctx: savedContext
});
}š§ Advanced Configuration
Custom Context Store
class RedisContextStore implements ICNSStimulationContextStore {
constructor(private client: RedisClient, private sessionId: string) {}
get<T>(): T | undefined {
// Implement Redis-backed context retrieval
}
set<T>(value: T): void {
// Implement Redis-backed context storage
}
}
await cns.stimulate(signal, {
createContextStore: () => new RedisContextStore(redisClient, 'session-123')
});CNS Configuration
const cns = new CNS(neurons, {
autoCleanupContexts: true, // Auto-cleanup unused contexts
defaultConcurrency: 50 // Default concurrency limit
});ā ļø Performance Warning: autoCleanupContexts adds computational overhead due to:
- O(V²) initialization cost - building SCC (Strongly Connected Components) structures
- O(1 + A) runtime cost per cleanup check (where A = number of SCC ancestors)
- Memory overhead for storing SCC graphs and ancestor relationships
Use only when:
- Memory leaks are a critical issue
- You have a small to medium-sized neuron graph (< 1000 neurons)
- Performance is less critical than memory management
For production systems, consider manual context cleanup or custom cleanup strategies instead.
CNStra provides deterministic, type-safe orchestration without the complexity of traditional event systems. Build reliable, maintainable reactive applications with clear data flow and predictable behavior.