Returning Multiple Signals
Sometimes a neuron needs to emit multiple signals in response to a single input. CNStra supports returning arrays of signals from neuron responses.
Basic Array Return
A neuron can return an array of signals instead of a single signal:
import { CNS, collateral, neuron } from '@cnstra/core';
const input = collateral<{ value: number }>('input');
const output1 = collateral<{ result: string }>('output1');
const output2 = collateral<{ result: string }>('output2');
const splitter = neuron('splitter', { output1, output2 }).dendrite({
collateral: input,
response: (payload, axon) => {
// Return an array of signals
return [
axon.output1.createSignal({ result: `First: ${payload.value}` }),
axon.output2.createSignal({ result: `Second: ${payload.value}` }),
];
},
});
Use Cases
Fan-out Pattern
Split a single input into multiple parallel processing paths:
const processPayment = collateral<{ orderId: string }>('processPayment');
const updateInventory = collateral<{ orderId: string }>('updateInventory');
const notifyUser = collateral<{ orderId: string }>('notifyUser');
const logTransaction = collateral<{ orderId: string }>('logTransaction');
const orderProcessor = neuron('orderProcessor', {
updateInventory,
notifyUser,
logTransaction,
}).dendrite({
collateral: processPayment,
response: (payload, axon) => {
// Process payment and trigger multiple downstream actions
return [
axon.updateInventory.createSignal({ orderId: payload.orderId }),
axon.notifyUser.createSignal({ orderId: payload.orderId }),
axon.logTransaction.createSignal({ orderId: payload.orderId }),
];
},
});
Dynamic Signal Generation
Generate a variable number of signals based on input:
const batchInput = collateral<{ items: string[] }>('batchInput');
const itemOutput = collateral<{ item: string; index: number }>('itemOutput');
const batchProcessor = neuron('batchProcessor', { itemOutput }).dendrite({
collateral: batchInput,
response: (payload, axon) => {
// Create a signal for each item
return payload.items.map((item, index) =>
axon.itemOutput.createSignal({ item, index })
);
},
});
Conditional Multiple Signals
Return different numbers of signals based on conditions:
const validation = collateral<{ data: any }>('validation');
const success = collateral<{ validated: any }>('success');
const error = collateral<{ error: string }>('error');
const audit = collateral<{ action: string }>('audit');
const validator = neuron('validator', { success, error, audit }).dendrite({
collateral: validation,
response: (payload, axon) => {
const signals = [];
if (isValid(payload.data)) {
signals.push(axon.success.createSignal({ validated: payload.data }));
} else {
signals.push(axon.error.createSignal({ error: 'Validation failed' }));
}
// Always log to audit
signals.push(axon.audit.createSignal({ action: 'validation_attempt' }));
return signals;
},
});
Async Array Return
Arrays of signals work seamlessly with async functions:
const dataFetch = collateral<{ ids: string[] }>('dataFetch');
const dataReady = collateral<{ id: string; data: any }>('dataReady');
const fetcher = neuron('fetcher', { dataReady }).dendrite({
collateral: dataFetch,
response: async (payload, axon) => {
// Fetch all data asynchronously
const results = await Promise.all(
payload.ids.map(id => fetchData(id))
);
// Return array of signals
return results.map((data, i) =>
axon.dataReady.createSignal({ id: payload.ids[i], data })
);
},
});
Empty Arrays
Returning an empty array is valid and will not propagate any signals:
const filter = neuron('filter', { output }).dendrite({
collateral: input,
response: (payload, axon) => {
if (!shouldProcess(payload)) {
return []; // No signals emitted
}
return [axon.output.createSignal(payload)];
},
});
Multiple Initial Signals
You can also start a stimulation with multiple signals:
const cns = new CNS([/* neurons */]);
// Stimulate with an array of signals
cns.stimulate([
input.createSignal({ value: 1 }),
input.createSignal({ value: 2 }),
input.createSignal({ value: 3 }),
]);
Best Practices
- Use for genuine fan-out: Return arrays when you truly need parallel processing, not just for convenience
- Consider ordering: Signals in the array are processed in order, but may execute concurrently based on queue settings
- Empty arrays are fine: Don't hesitate to return an empty array when appropriate
- Mix with single signals: You can mix neurons that return single signals with those that return arrays
- Type safety: TypeScript will enforce correct signal types in arrays
Performance Considerations
- Each signal in the array triggers its own set of subscribers
- If you have many signals, consider using concurrency limits
- Arrays are processed immediately; signals are not batched