@cnstra/core 1.2.2 โ†’ 1.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (39) hide show
  1. package/README.md +242 -557
  2. package/dist/CNS.d.ts +54 -61
  3. package/dist/CNS.d.ts.map +1 -1
  4. package/dist/CNSCollateral.d.ts +4 -2
  5. package/dist/CNSCollateral.d.ts.map +1 -1
  6. package/dist/CNSFunctionalQueue.d.ts +26 -0
  7. package/dist/CNSFunctionalQueue.d.ts.map +1 -0
  8. package/dist/CNSStimulation.d.ts +52 -0
  9. package/dist/CNSStimulation.d.ts.map +1 -0
  10. package/dist/CNSStimulationContextStore.d.ts +1 -0
  11. package/dist/CNSStimulationContextStore.d.ts.map +1 -1
  12. package/dist/factory/index.d.ts.map +1 -1
  13. package/dist/index.cjs +490 -214
  14. package/dist/index.cjs.map +1 -1
  15. package/dist/index.js +490 -214
  16. package/dist/index.js.map +1 -1
  17. package/dist/interfaces/ICNS.d.ts +14 -0
  18. package/dist/interfaces/ICNS.d.ts.map +1 -0
  19. package/dist/interfaces/ICNSCollateral.d.ts +6 -0
  20. package/dist/interfaces/ICNSCollateral.d.ts.map +1 -0
  21. package/dist/interfaces/ICNSStimulationContextStore.d.ts +1 -0
  22. package/dist/interfaces/ICNSStimulationContextStore.d.ts.map +1 -1
  23. package/dist/types/TCNSDendrite.d.ts +2 -1
  24. package/dist/types/TCNSDendrite.d.ts.map +1 -1
  25. package/dist/types/TCNSNeuronResponseReturn.d.ts +3 -0
  26. package/dist/types/TCNSNeuronResponseReturn.d.ts.map +1 -0
  27. package/dist/types/TCNSOptions.d.ts +11 -0
  28. package/dist/types/TCNSOptions.d.ts.map +1 -0
  29. package/dist/types/TCNSSignal.d.ts +3 -2
  30. package/dist/types/TCNSSignal.d.ts.map +1 -1
  31. package/dist/types/TCNSStimulationOptions.d.ts +13 -0
  32. package/dist/types/TCNSStimulationOptions.d.ts.map +1 -0
  33. package/dist/types/TCNSStimulationQueueItem.d.ts +10 -0
  34. package/dist/types/TCNSStimulationQueueItem.d.ts.map +1 -0
  35. package/dist/types/TCNSStimulationResponse.d.ts +11 -0
  36. package/dist/types/TCNSStimulationResponse.d.ts.map +1 -0
  37. package/dist/types/TCNSSubscriber.d.ts +7 -0
  38. package/dist/types/TCNSSubscriber.d.ts.map +1 -0
  39. package/package.json +1 -1
package/README.md CHANGED
@@ -9,23 +9,26 @@ You explicitly start a run with `cns.stimulate(...)`; CNStra then performs a **d
9
9
 
10
10
  > **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.
11
11
 
12
- > **Not pub/sub:** there are no ambient listeners or global `emit`. Only the **signal you return** from a dendrite continues the traversal; returning `null`/`undefined` ends that branch. `maxHops` guards against cycles.
12
+ > **Not pub/sub:** there are no ambient listeners or global `emit`. Only the **signal you return** from a dendrite continues the traversal; returning `null`/`undefined` ends that branch. Hop limits guard against cycles.
13
13
 
14
14
  ## ๐Ÿ—๏ธ Core Model
15
15
 
16
16
  ### Neurons
17
17
  Units of logic with clear DI and sharp boundaries:
18
18
  - **ID** โ€” unique name
19
- - **Axon** โ€” the neuronโ€™s **output channels** (its collaterals)
19
+ - **Axon** โ€” the neuron's **output channels** (its collaterals)
20
20
  - **Dendrites** โ€” **input receptors** (typed reactions bound to specific collaterals)
21
21
 
22
22
  ### Collaterals
23
23
  Typed **output channels** that mint signals:
24
24
  - **ID** โ€” string identifier (e.g., `'user:created'`)
25
25
  - **Payload** โ€” the shape carried by the signal
26
- - `createSignal(payload)` โ†’ `{ type, payload }`
26
+ - `createSignal(payload)` โ†’ `{ collateral, payload }`
27
27
 
28
- > **Afferent axon:** the object of collaterals you expose publicly. Its **keys** (e.g., `userCreated`) are what you pass to `cns.stimulate(...)`, not the string IDs.
28
+ ### Signals
29
+ The data structures that flow through the system:
30
+ - **collateral** โ€” reference to the collateral that created this signal
31
+ - **payload** โ€” the typed data being transmitted
29
32
 
30
33
  ## ๐Ÿš€ Quick Start
31
34
 
@@ -64,10 +67,10 @@ const userService = neuron('user-service', {
64
67
  const cns = new CNS([userService]);
65
68
 
66
69
  // Stimulate the system
67
- await cns.stimulate(userCreated, {
70
+ await cns.stimulate(userCreated.createSignal({
68
71
  id: '123',
69
72
  name: 'John Doe'
70
- });
73
+ }));
71
74
  ```
72
75
 
73
76
  ## ๐Ÿ“š API Reference
@@ -113,58 +116,76 @@ The main orchestrator that manages signal flow between neurons.
113
116
 
114
117
  #### Constructor
115
118
  ```typescript
116
- new CNS(neurons)
119
+ new CNS(neurons, options?)
117
120
  ```
118
121
 
119
122
  **Parameters:**
120
123
  - `neurons`: Array of neurons that process signals
124
+ - `options`: Optional CNS configuration
121
125
 
122
126
  #### `stimulate()` Method
123
127
  ```typescript
124
- cns.stimulate(collateral, payload, options?)
128
+ cns.stimulate(signal, options?)
125
129
  ```
126
130
 
127
131
  **Parameters:**
128
- - `collateral`: The collateral instance to trigger
129
- - `payload`: Signal payload data
130
- - `options`: Optional configuration object
132
+ - `signal`: A signal created by `collateral.createSignal(payload)`
133
+ - `options`: Optional stimulation configuration
134
+
135
+ **Returns:** `Promise<void>` that resolves when stimulation completes
136
+
137
+ **Example:**
138
+ ```typescript
139
+ await cns.stimulate(
140
+ userCreated.createSignal({ id: '123', name: 'John' })
141
+ );
142
+ ```
143
+
144
+ ## โš™๏ธ Stimulation Options
131
145
 
132
- **Options:**
146
+ ### `maxNeuronHops?: number` (default: 1000)
147
+ Prevents infinite loops by limiting signal traversal depth.
133
148
 
134
- ##### `maxHops?: number` (default: 1000)
135
- Prevents infinite loops by limiting signal traversal depth. Each signal hop increments the counter.
136
149
  ```typescript
137
- // Prevent runaway recursion
138
- cns.stimulate(input, data, {
139
- maxHops: 50 // Stop after 50 signal hops
150
+ await cns.stimulate(signal, {
151
+ maxNeuronHops: 50 // Stop after 50 neuron hops
140
152
  });
141
153
  ```
142
- โš ๏ธ **Memory Impact**: Higher values allow deeper graphs but increase memory usage.
143
154
 
144
- ##### `onTrace?: (trace) => void`
145
- Real-time callback for monitoring signal flow, errors, and completion. Essential for fire-and-forget pattern.
155
+ ### `onResponse?: (response) => void`
156
+ Real-time callback for monitoring signal flow and completion.
157
+
146
158
  ```typescript
147
- cns.stimulate(input, data, {
148
- onTrace: (trace) => {
149
- console.log(`Signal: ${trace.collateralId}, Hops: ${trace.hops}`);
159
+ await cns.stimulate(signal, {
160
+ onResponse: (response) => {
161
+ console.log(`Signal: ${response.outputSignal?.collateral.id}`);
162
+ console.log(`Hops: ${response.hops}`);
150
163
 
151
- if (trace.error) {
152
- console.error('Processing failed:', trace.error);
164
+ if (response.error) {
165
+ console.error('Processing failed:', response.error);
153
166
  }
154
167
 
155
- if (trace.queueLength === 0) {
168
+ if (response.queueLength === 0) {
156
169
  console.log('Stimulation completed');
157
170
  }
158
171
  }
159
172
  });
160
173
  ```
161
174
 
162
- ##### `abortSignal?: AbortSignal`
163
- Gracefully stop stimulation using AbortController. Already-running operations complete, but no new work starts.
175
+ **Response Object:**
176
+ - `outputSignal` โ€” The signal being processed (if any)
177
+ - `hops` โ€” Number of neuron hops taken so far
178
+ - `queueLength` โ€” Remaining signals in processing queue (0 = complete)
179
+ - `error` โ€” Any error that occurred during processing
180
+ - `stimulationId` โ€” Unique identifier for this stimulation
181
+
182
+ ### `abortSignal?: AbortSignal`
183
+ Gracefully stop stimulation using AbortController.
184
+
164
185
  ```typescript
165
186
  const controller = new AbortController();
166
187
 
167
- cns.stimulate(input, data, {
188
+ cns.stimulate(signal, {
168
189
  abortSignal: controller.signal
169
190
  });
170
191
 
@@ -172,632 +193,296 @@ cns.stimulate(input, data, {
172
193
  setTimeout(() => controller.abort(), 5000);
173
194
  ```
174
195
 
175
- ##### `ctx?: ICNSStimulationContextStore`
176
- Provide existing context store for recovery/retry scenarios. Restores neuron state from previous stimulation.
196
+ ### `stimulationId?: string`
197
+ Custom identifier for this stimulation cascade. Auto-generated if not provided.
198
+
177
199
  ```typescript
178
- // Retry with preserved context
179
- cns.stimulate(failedCollateral, payload, {
180
- ctx: savedContextStore, // Restore previous state
181
- onTrace: (trace) => { /* monitor retry */ }
200
+ await cns.stimulate(signal, {
201
+ stimulationId: 'user-action-' + Date.now()
182
202
  });
183
203
  ```
184
204
 
185
- ##### `createContextStore?: () => ICNSStimulationContextStore`
186
- Factory for custom context store implementations. Useful for persistence, encryption, or specialized storage.
205
+ ### `allowType?: (collateralId: string) => boolean`
206
+ Filter which collateral types can be processed.
207
+
187
208
  ```typescript
188
- cns.stimulate(input, data, {
189
- createContextStore: () => new RedisContextStore('session-123'),
190
- onTrace: (trace) => {
191
- // trace.contextStore is your custom RedisContextStore
192
- }
209
+ await cns.stimulate(signal, {
210
+ allowType: (type) => type.startsWith('user:') // Only process user-related signals
193
211
  });
194
212
  ```
195
213
 
196
- ##### `spikeId?: string`
197
- Custom identifier for this stimulation cascade. Auto-generated if not provided. Useful for debugging and correlation.
214
+ ### `concurrency?: number` (default: unlimited)
215
+ Limit concurrent operations to prevent resource exhaustion.
216
+
198
217
  ```typescript
199
- cns.stimulate(input, data, {
200
- spikeId: 'user-action-' + Date.now(),
201
- onTrace: (trace) => {
202
- console.log('Spike ID:', trace.spikeId);
203
- }
218
+ await cns.stimulate(signal, {
219
+ concurrency: 10 // Max 10 operations at once
204
220
  });
205
221
  ```
206
222
 
207
- ##### `allowType?: (collateralId: string) => boolean`
208
- Filter which collateral types can be processed. Useful for selective stimulation or debugging specific paths.
223
+ ### `ctx?: ICNSStimulationContextStore`
224
+ Provide existing context store for recovery/retry scenarios.
225
+
209
226
  ```typescript
210
- cns.stimulate(input, data, {
211
- allowType: (type) => type.startsWith('user:'), // Only process user-related signals
212
- onTrace: (trace) => {
213
- // Only user: collaterals will appear in traces
214
- }
227
+ await cns.stimulate(signal, {
228
+ ctx: savedContextStore // Restore previous state
215
229
  });
216
230
  ```
217
231
 
218
- ##### `concurrency?: number` (default: unlimited)
219
- Limit concurrent operations to prevent resource exhaustion. Both sync and async operations count toward this limit.
232
+ ### `createContextStore?: () => ICNSStimulationContextStore`
233
+ Factory for custom context store implementations.
234
+
220
235
  ```typescript
221
- cns.stimulate(input, data, {
222
- concurrency: 10, // Max 10 operations at once
223
- onTrace: (trace) => {
224
- // Processing respects concurrency limit
225
- }
236
+ await cns.stimulate(signal, {
237
+ createContextStore: () => new CustomContextStore()
226
238
  });
227
239
  ```
228
240
 
229
- **Why These Options Matter:**
230
- - **Control**: Fine-tune behavior for your specific use case
231
- - **Performance**: Manage memory and concurrency based on system constraints
232
- - **Reliability**: Implement timeouts, retries, and error recovery
233
- - **Debugging**: Filter signals, add correlation IDs, and monitor execution
234
- - **Integration**: Work with existing systems via custom context stores
235
-
236
- ## ๐Ÿง  Memory Policy & Error Handling
237
-
238
- ### Memory-First Design
239
- CNS is designed with strict memory efficiency and universal compatibility in mind:
240
-
241
- - **Zero dependencies**: No third-party packages - runs in any JavaScript/TypeScript environment
242
- - **No error storage**: Errors are immediately passed to your `onTrace` callback and not stored in memory
243
- - **Streaming traces**: Signal traces are delivered via callbacks, not accumulated in arrays
244
- - **Context on-demand**: Context stores are created only when needed and passed via traces
245
- - **No global state**: The system maintains no persistent state between stimulations
246
- - **Context scope**: Context stores exist only for the duration of a single stimulation - they are automatically cleaned up when stimulation completes
247
- - **Context content warning**: โš ๏ธ Context values dictionary is kept in memory during stimulation - store minimal data only (IDs, counters, flags) rather than large objects or payloads
248
- - **Context mutation**: CNS modifies context dictionaries in-place - create new context objects if you need versioning or rollback capabilities
249
-
250
- ### maxHops Memory Impact
251
- โš ๏ธ **Warning**: Using `maxHops` parameter increases memory consumption proportionally:
252
- - Default hop limit: 1000
253
- - Higher `maxHops` = more potential concurrent signals = more memory usage
254
- - For infinite recursion scenarios, keep `maxHops` reasonable (100-1000 range)
255
-
256
- ### Error Handling Philosophy
257
- CNS handles errors through immediate callback delivery rather than storage because:
258
- 1. **Memory efficiency**: No error accumulation in library memory
259
- 2. **Real-time feedback**: Immediate error notification to your application
260
- 3. **Recovery control**: You decide how to handle and store errors
261
- 4. **Context preservation**: Error traces include full context for recovery scenarios
241
+ ## ๐Ÿ”„ Signal Flow Patterns
242
+
243
+ ### Basic Chain Processing
262
244
 
263
245
  ```typescript
264
- // Error handling example
265
- cns.stimulate(input, payload, {
266
- onTrace: (trace) => {
267
- if (trace.error) {
268
- // Handle immediately - error won't be stored by CNS
269
- logError(trace.error);
270
- saveErrorContext(trace.contextStore);
271
- }
246
+ const input = collateral<{ value: number }>('input');
247
+ const middle = collateral<{ doubled: number }>('middle');
248
+ const output = collateral<{ result: string }>('output');
249
+
250
+ const step1 = neuron('step1', { middle }).dendrite({
251
+ collateral: input,
252
+ response: (payload, axon) => {
253
+ return axon.middle.createSignal({ doubled: payload.value * 2 });
272
254
  }
273
255
  });
274
256
 
275
- // โœ… Good: Minimal context data
276
- const goodContext = {
277
- retryCount: 3,
278
- userId: "123",
279
- lastAttempt: Date.now()
280
- };
281
-
282
- // โŒ Bad: Large objects in context
283
- const badContext = {
284
- fullUserProfile: largeUserObject,
285
- historicalData: massiveArray,
286
- fileBuffer: binaryData
287
- };
288
- ```
257
+ const step2 = neuron('step2', { output }).dendrite({
258
+ collateral: middle,
259
+ response: (payload, axon) => {
260
+ return axon.output.createSignal({ result: `Final: ${payload.doubled}` });
261
+ }
262
+ });
263
+
264
+ const cns = new CNS([step1, step2]);
289
265
 
290
- ## ๐Ÿ”„ Key Behavior
266
+ await cns.stimulate(input.createSignal({ value: 5 }));
267
+ // Flows: input(5) โ†’ middle(10) โ†’ output("Final: 10")
268
+ ```
291
269
 
292
- ### Sync-First Processing with Async Support
293
- CNS processes signals synchronously by default, avoiding microtask scheduling until absolutely necessary. This provides predictable, immediate execution for synchronous operations while seamlessly handling async when needed.
270
+ ### Fan-out Processing
294
271
 
295
- **Synchronous Path** - No async/await overhead:
296
272
  ```typescript
297
- const syncNeuron = neuron('sync-processor', { output })
298
- .dendrite({
299
- collateral: input,
300
- response: (payload, axon) => {
301
- // Pure synchronous processing - runs immediately
302
- // No Promise creation, no microtask scheduling
303
- const result = processSync(payload);
304
- return axon.output.createSignal({ result });
305
- }
306
- });
273
+ const trigger = collateral<{ data: string }>('trigger');
274
+ const branch1 = collateral<{ result: string }>('branch1');
275
+ const branch2 = collateral<{ result: string }>('branch2');
307
276
 
308
- // Chain of sync neurons executes immediately in single tick
309
- const chainStart = collateral<{ value: number }>('start');
310
- const chainMiddle = collateral<{ doubled: number }>('middle');
311
- const chainEnd = collateral<{ final: string }>('end');
277
+ const processor1 = neuron('proc1', { branch1 }).dendrite({
278
+ collateral: trigger,
279
+ response: (payload, axon) => {
280
+ return axon.branch1.createSignal({ result: `A-${payload.data}` });
281
+ }
282
+ });
312
283
 
313
- const step1 = neuron('step1', { chainMiddle })
314
- .dendrite({
315
- collateral: chainStart,
316
- response: (payload, axon) => {
317
- return axon.chainMiddle.createSignal({ doubled: payload.value * 2 });
318
- }
319
- });
284
+ const processor2 = neuron('proc2', { branch2 }).dendrite({
285
+ collateral: trigger,
286
+ response: (payload, axon) => {
287
+ return axon.branch2.createSignal({ result: `B-${payload.data}` });
288
+ }
289
+ });
320
290
 
321
- const step2 = neuron('step2', { chainEnd })
322
- .dendrite({
323
- collateral: chainMiddle,
324
- response: (payload, axon) => {
325
- return axon.chainEnd.createSignal({ final: `Result: ${payload.doubled}` });
326
- }
327
- });
291
+ const cns = new CNS([processor1, processor2]);
328
292
 
329
- // This entire chain executes synchronously in one tick
330
- cns.stimulate(chainStart, { value: 5 });
293
+ await cns.stimulate(trigger.createSignal({ data: 'test' }));
294
+ // Flows: trigger("test") โ†’ [branch1("A-test"), branch2("B-test")]
331
295
  ```
332
296
 
333
- **Mixed Sync/Async** - Async only where needed:
297
+ ### Context-Aware Processing
298
+
334
299
  ```typescript
335
- const mixedNeuron = neuron('mixed-processor', { output })
300
+ import { withCtx } from '@cnstra/core';
301
+
302
+ const input = collateral<{ increment: number }>('input');
303
+ const output = collateral<{ count: number }>('output');
304
+
305
+ const counter = withCtx<{ total: number }>()
306
+ .neuron('counter', { output })
336
307
  .dendrite({
337
308
  collateral: input,
338
- response: async (payload, axon) => {
339
- // Synchronous preprocessing - runs immediately
340
- const validated = validateInput(payload);
341
- const transformed = transformData(validated);
309
+ response: async (payload, axon, ctx) => {
310
+ const current = ctx.get()?.total || 0;
311
+ const newTotal = current + payload.increment;
342
312
 
343
- // Only here do we enter async context
344
- const result = await fetchFromAPI(transformed);
313
+ ctx.set({ total: newTotal });
345
314
 
346
- // Synchronous postprocessing
347
- const formatted = formatResult(result);
348
- return axon.output.createSignal({ formatted });
315
+ return axon.output.createSignal({ count: newTotal });
349
316
  }
350
317
  });
318
+
319
+ const cns = new CNS([counter]);
320
+
321
+ await cns.stimulate(input.createSignal({ increment: 5 })); // count: 5
322
+ await cns.stimulate(input.createSignal({ increment: 3 })); // count: 8 (separate context)
351
323
  ```
352
324
 
353
- **Key Performance Benefits:**
354
- - **Sync chains execute in single tick**: No microtask delays between neurons
355
- - **Minimal Promise overhead**: Async context created only when needed
356
- - **Predictable execution**: Sync operations complete before any async work starts
357
- - **No unnecessary await**: Sync neurons don't use async/await syntax
325
+ ## ๐Ÿง  Memory & Performance
358
326
 
359
- ### Isolated Stimulation Scope
360
- Each `cns.stimulate()` call creates an isolated signal cascade. You never need to worry about "messageId" or signal correlation - all signals in your trace belong exclusively to your stimulation.
327
+ ### Memory-Efficient Design
328
+ - **Zero dependencies**: No third-party packages
329
+ - **No error storage**: Errors delivered via callbacks, not stored
330
+ - **Streaming responses**: Signal traces delivered via callbacks
331
+ - **Context on-demand**: Context stores created only when needed
332
+ - **No global state**: Clean slate between stimulations
361
333
 
362
- #### Complex Orchestration Example: Card & Deck Creation
334
+ ### Performance Characteristics
335
+ - **Sync-first**: Synchronous chains execute in single tick
336
+ - **Minimal async overhead**: Promises created only when needed
337
+ - **Stack-safe**: Handles deep chains without stack overflow
338
+ - **Bounded execution**: `maxNeuronHops` prevents runaway processing
363
339
 
364
- ```typescript
365
- // Define collaterals for the orchestration
366
- const httpRequest = collateral<{ cardText: string }>('http:request');
367
- const requestProcessed = collateral<{ cardText: string }>('request:processed');
368
- const deckCreated = collateral<{ deckId: string; cardText: string }>('deck:created');
369
- const cardCreated = collateral<{ cardId: string; deckId: string }>('card:created');
370
-
371
- // Controller neuron: orchestrates the entire process
372
- const controllerNeuron = withCtx<{
373
- cardText?: string;
374
- deckId?: string;
375
- cardId?: string;
376
- }>().neuron('controller', { requestProcessed, cardCreated })
377
- .dendrite({
378
- collateral: httpRequest,
379
- response: async (payload, axon, ctx) => {
380
- // Step 1: Store card text in context and signal processing started
381
- ctx.set({ cardText: payload.cardText });
382
- return axon.requestProcessed.createSignal({ cardText: payload.cardText });
383
- }
384
- })
385
- .dendrite({
386
- collateral: deckCreated,
387
- response: async (payload, axon, ctx) => {
388
- // Step 3: Deck created, now create the card
389
- const context = ctx.get()!;
390
- ctx.set({ ...context, deckId: payload.deckId });
391
- return axon.cardCreated.createSignal({
392
- deckId: payload.deckId,
393
- cardText: context.cardText!
394
- });
395
- }
396
- });
340
+ ### Best Practices
341
+ - Keep context data minimal (IDs, counters, flags)
342
+ - Use synchronous responses when possible
343
+ - Set reasonable `maxNeuronHops` limits
344
+ - Implement proper error handling in `onResponse`
397
345
 
398
- // Deck service: creates deck when request is processed
399
- const deckNeuron = neuron('deck-service', { deckCreated })
400
- .dendrite({
401
- collateral: requestProcessed,
402
- response: async (payload, axon) => {
403
- // Step 2: Create deck (async operation)
404
- const deckId = await createDeck();
405
- return axon.deckCreated.createSignal({
406
- deckId,
407
- cardText: payload.cardText
408
- });
409
- }
410
- });
346
+ ## ๐ŸŽฏ Common Use Cases
411
347
 
412
- // Card service: creates card when deck is ready
413
- const cardNeuron = neuron('card-service', { cardCreated })
414
- .dendrite({
415
- collateral: cardCreated,
416
- response: async (payload, axon) => {
417
- // Step 4: Create card (async operation)
418
- const cardId = await createCard(payload.deckId, payload.cardText);
419
- return axon.cardCreated.createSignal({
420
- cardId,
421
- deckId: payload.deckId
422
- });
423
- }
424
- });
348
+ ### HTTP Request Processing
349
+ ```typescript
350
+ const httpRequest = collateral<{ method: string; url: string }>('http:request');
351
+ const requestValidated = collateral<{ method: string; url: string }>('request:validated');
352
+ const responseReady = collateral<{ status: number; body: any }>('response:ready');
425
353
 
426
- // HTTP handler using CNS orchestration
427
- app.post('/create-card', async (req, res) => {
428
- const cns = new CNS([controllerNeuron, deckNeuron, cardNeuron]);
429
-
430
- let deckId: string | undefined;
431
- let cardId: string | undefined;
432
-
433
- cns.stimulate(httpRequest, { cardText: req.body.text }, {
434
- onTrace: (trace) => {
435
- // Collect results as they become available
436
- if (trace.collateralId === 'deck:created') {
437
- deckId = (trace.payload as any).deckId;
438
- }
439
- if (trace.collateralId === 'card:created') {
440
- cardId = (trace.payload as any).cardId;
441
- }
442
-
443
- // Process complete when queue is empty
444
- if (trace.queueLength === 0) {
445
- res.json({ deckId, cardId, success: true });
446
- }
354
+ const validator = neuron('validator', { requestValidated }).dendrite({
355
+ collateral: httpRequest,
356
+ response: (payload, axon) => {
357
+ if (!payload.url.startsWith('https://')) {
358
+ throw new Error('Only HTTPS URLs allowed');
447
359
  }
448
- });
360
+ return axon.requestValidated.createSignal(payload);
361
+ }
449
362
  });
450
- ```
451
-
452
- **Key Benefits of Isolated Stimulation:**
453
-
454
- 1. **No Signal Pollution**: Every signal in your trace belongs to your request - no cross-contamination
455
- 2. **No Correlation IDs**: No need to track "messageId" or "requestId" through the system
456
- 3. **Guaranteed Ordering**: Signals process in deterministic order within your stimulation
457
- 4. **Clean Scoping**: Each HTTP request gets its own isolated signal cascade
458
- 5. **Context Safety**: Context store is private to your stimulation - no shared state issues
459
363
 
460
- ### Only Returned Signals Are Processed
461
- **Important**: In CNS, only the signal returned from the `reaction` function is processed and propagated to other neurons. Signals created with `axon.collateral.createSignal()` but not returned are NOT processed by the system.
462
-
463
- ```typescript
464
- const processor = neuron('processor', {
465
- output: outputCollateral
466
- })
467
- .dendrite({
468
- collateral: inputCollateral,
364
+ const handler = neuron('handler', { responseReady }).dendrite({
365
+ collateral: requestValidated,
469
366
  response: async (payload, axon) => {
470
- // This signal is created but NOT processed
471
- axon.output.createSignal({ message: 'Hello' });
472
-
473
- // Only this returned signal is processed
474
- return axon.output.createSignal({ message: 'World' });
367
+ const response = await fetch(payload.url, { method: payload.method });
368
+ const body = await response.json();
369
+ return axon.responseReady.createSignal({ status: response.status, body });
475
370
  }
476
371
  });
477
- ```
478
-
479
- ### Multiple Neurons on Same Collateral
480
- Multiple neurons can listen to the same collateral:
481
372
 
482
- ```typescript
483
- const emailService = neuron('email-service', { emailSent })
484
- .dendrite({
485
- collateral: userCreated,
486
- response: (payload, axon) => {
487
- return axon.emailSent.createSignal({ to: 'user@example.com' });
488
- }
489
- });
490
-
491
- const notificationService = neuron('notification-service', { notificationSent })
492
- .dendrite({
493
- collateral: userCreated,
494
- response: (payload, axon) => {
495
- return axon.notificationSent.createSignal({ message: 'User created' });
496
- }
497
- });
498
-
499
- // Both neurons will process the userCreated signal
500
- const cns = new CNS([emailService, notificationService]);
373
+ const cns = new CNS([validator, handler]);
501
374
  ```
502
375
 
503
- ### Conditional Logic
376
+ ### Event Sourcing
504
377
  ```typescript
505
- const router = neuron('router', {
506
- success: successCollateral,
507
- error: errorCollateral
508
- })
509
- .dendrite({
510
- collateral: requestCollateral,
378
+ const eventReceived = collateral<{ type: string; data: any }>('event:received');
379
+ const eventStored = collateral<{ eventId: string }>('event:stored');
380
+ const stateUpdated = collateral<{ aggregateId: string }>('state:updated');
381
+
382
+ const eventStore = neuron('event-store', { eventStored }).dendrite({
383
+ collateral: eventReceived,
511
384
  response: async (payload, axon) => {
512
- try {
513
- const result = await processRequest(payload);
514
- return axon.success.createSignal(result);
515
- } catch (error) {
516
- return axon.error.createSignal({ error: error.message });
517
- }
385
+ const eventId = await saveEvent(payload);
386
+ return axon.eventStored.createSignal({ eventId });
518
387
  }
519
388
  });
520
- ```
521
-
522
- ### Context Management
523
389
 
524
- Neurons can maintain local state using the context parameter:
390
+ const stateManager = neuron('state-manager', { stateUpdated }).dendrite({
391
+ collateral: eventStored,
392
+ response: async (payload, axon) => {
393
+ const aggregateId = await updateState(payload.eventId);
394
+ return axon.stateUpdated.createSignal({ aggregateId });
395
+ }
396
+ });
525
397
 
526
- ```typescript
527
- const statefulNeuron = withCtx().neuron('stateful', { output: outputCollateral })
528
- .dendrite({
529
- collateral: inputCollateral,
530
- response: (payload, axon, ctx) => {
531
- // Get current state
532
- const currentState = ctx.get();
533
-
534
- // Update state
535
- ctx.set({ count: (currentState?.count || 0) + 1 });
536
-
537
- return axon.output.createSignal({ count: ctx.get()?.count });
538
- }
539
- });
398
+ const cns = new CNS([eventStore, stateManager]);
540
399
  ```
541
400
 
542
- ### Context-Based Retry Mechanisms
401
+ ## ๐Ÿšจ Error Handling
543
402
 
544
- CNS supports robust retry mechanisms using context stores to maintain state across stimulation attempts. This is particularly useful for handling transient failures:
403
+ Errors are delivered immediately via `onResponse` callbacks:
545
404
 
546
- #### Stateful Retry Pattern
547
405
  ```typescript
548
- type RetryContext = {
549
- tryNumber: number;
550
- };
551
-
552
- const retryableNeuron = withCtx<RetryContext>().neuron('retryable', { output })
553
- .dendrite({
554
- collateral: input,
555
- response: async (payload, axon, ctx) => {
556
- const current = ctx.get() || { tryNumber: 0 };
557
- const tryNumber = current.tryNumber + 1;
406
+ await cns.stimulate(signal, {
407
+ onResponse: (response) => {
408
+ if (response.error) {
409
+ console.error(`Error in neuron processing:`, response.error);
558
410
 
559
- // Update context with incremented try number
560
- ctx.set({ tryNumber });
561
-
562
- if (tryNumber === 1) {
563
- // First attempt: simulate failure
564
- throw new Error('Transient failure');
565
- } else {
566
- // Second attempt: succeed
567
- return axon.output.createSignal({
568
- result: `Success on try ${tryNumber}`,
569
- payload
570
- });
571
- }
572
- }
573
- });
574
- ```
575
-
576
- #### Retry Orchestration
577
- ```typescript
578
- let contextStore: ICNSStimulationContextStore | undefined;
579
-
580
- const handleStimulation = (isRetry = false) => {
581
- cns.stimulate(input, payload, {
582
- ctx: contextStore, // Use preserved context for retries
583
- onTrace: (trace) => {
584
- // Detect stimulation end
585
- if (trace.queueLength === 0) {
586
- if (trace.error && !isRetry) {
587
- // Capture context and retry
588
- contextStore = trace.contextStore;
589
- setTimeout(() => handleStimulation(true), 100);
590
- } else if (!trace.error && trace.collateralId === 'output') {
591
- // Success - process result
592
- console.log('Retry succeeded:', trace.payload);
593
- }
594
- }
595
- }
596
- });
597
- };
598
-
599
- handleStimulation();
600
- ```
601
-
602
- #### Context Versioning for Selective Recovery
603
- Since CNS modifies the context dictionary in-place, you can create new context objects for version control and selective recovery:
604
-
605
- ```typescript
606
- // Create context snapshots for version control
607
- const createContextSnapshot = (store: ICNSStimulationContextStore) => {
608
- const snapshot = new CNSStimulationContextStore();
609
- snapshot.setAll(store.getAll());
610
- return snapshot;
611
- };
612
-
613
- const retryWithVersionControl = () => {
614
- let preFailureContext: ICNSStimulationContextStore | undefined;
615
-
616
- cns.stimulate(input, payload, {
617
- onTrace: (trace) => {
618
- // Save context before potential failure points
619
- if (trace.collateralId === 'risky-operation') {
620
- preFailureContext = createContextSnapshot(trace.contextStore);
621
- }
411
+ // Log error details
412
+ console.error(`Signal: ${response.outputSignal?.collateral.id}`);
413
+ console.error(`Stimulation: ${response.stimulationId}`);
622
414
 
623
- if (trace.queueLength === 0 && trace.error) {
624
- // Option 1: Retry with context from just before failure
625
- // (excludes the failed step's context changes)
626
- cns.stimulate(failedCollateral, payload, {
627
- ctx: preFailureContext,
628
- onTrace: (retryTrace) => { /* handle retry */ }
629
- });
630
-
631
- // Option 2: Retry with full context including failed step
632
- // (useful when failure was transient, not logic error)
633
- cns.stimulate(failedCollateral, payload, {
634
- ctx: trace.contextStore,
635
- onTrace: (retryTrace) => { /* handle retry */ }
636
- });
415
+ // Handle specific error types
416
+ if (response.error instanceof ValidationError) {
417
+ handleValidationError(response.error);
637
418
  }
638
419
  }
639
- });
640
- };
420
+ }
421
+ });
641
422
  ```
642
423
 
643
- ### State Recovery and Restart
644
-
645
- CNS provides built-in support for recovering from failures and restarting operations. The `onTrace` callback includes the current context state, allowing you to:
646
-
647
- 1. **Capture context on failure**: When an error occurs, the trace contains the complete context state
648
- 2. **Restart with preserved context**: Use the saved context to restore neuron states
649
- 3. **Resume from failure point**: Continue processing from where it left off
650
- 4. **Implement retry logic**: Context preserves attempt counts and other stateful retry data
651
-
424
+ **Error Recovery with Context:**
652
425
  ```typescript
653
- // Example: Advanced recovery with persistence
654
- let savedContextStore: ICNSStimulationContextStore | undefined;
655
-
656
- cns.stimulate(input, payload, {
657
- onTrace: (trace) => {
658
- if (trace.queueLength === 0) {
659
- if (trace.error) {
660
- // Failure: save context for recovery
661
- savedContextStore = trace.contextStore;
662
- await persistContextToStorage(trace.contextStore.getAll());
663
- console.log('Context saved for recovery');
664
- }
426
+ let savedContext: ICNSStimulationContextStore | undefined;
427
+
428
+ await cns.stimulate(signal, {
429
+ onResponse: (response) => {
430
+ if (response.error) {
431
+ // Save context for retry
432
+ savedContext = response.contextStore;
665
433
  }
666
434
  }
667
435
  });
668
436
 
669
- // Recovery from persistent storage
670
- const restoreAndRetry = async () => {
671
- const persistedState = await loadContextFromStorage();
672
- const contextStore = createContextStore();
673
- contextStore.setAll(persistedState);
674
-
675
- cns.stimulate(failedCollateral, originalPayload, {
676
- ctx: contextStore, // Restore full state
677
- onTrace: (trace) => {
678
- // Monitor recovery attempt
679
- }
437
+ // Retry with preserved context
438
+ if (savedContext) {
439
+ await cns.stimulate(retrySignal, {
440
+ ctx: savedContext
680
441
  });
681
- };
442
+ }
682
443
  ```
683
444
 
684
- **Custom Context Store Implementations**: You can create your own context store implementations by implementing the `ICNSStimulationContextStore` interface:
445
+ ## ๐Ÿ”ง Advanced Configuration
685
446
 
447
+ ### Custom Context Store
686
448
  ```typescript
687
- import { ICNSStimulationContextStore } from '@cnstra/core';
688
-
689
- // Example: Redis-backed context store for distributed systems
690
449
  class RedisContextStore implements ICNSStimulationContextStore {
691
- constructor(private redis: RedisClient, private keyPrefix: string) {}
692
-
693
- get(key: string): unknown {
694
- // Synchronous get - consider caching for performance
695
- return this.redis.getSync(`${this.keyPrefix}:${key}`);
696
- }
697
-
698
- set(key: string, value: unknown): void {
699
- this.redis.set(`${this.keyPrefix}:${key}`, JSON.stringify(value));
700
- }
701
-
702
- getAll(): Record<string, unknown> {
703
- const keys = this.redis.keys(`${this.keyPrefix}:*`);
704
- const result: Record<string, unknown> = {};
705
- for (const key of keys) {
706
- const shortKey = key.replace(`${this.keyPrefix}:`, '');
707
- result[shortKey] = JSON.parse(this.redis.getSync(key));
708
- }
709
- return result;
710
- }
711
-
712
- setAll(values: Record<string, unknown>): void {
713
- for (const [key, value] of Object.entries(values)) {
714
- this.set(key, value);
715
- }
716
- }
717
- }
718
-
719
- // Example: Immutable context store for versioning
720
- class ImmutableContextStore implements ICNSStimulationContextStore {
721
- private data: Record<string, unknown> = {};
722
- private version = 0;
723
-
724
- get(key: string): unknown {
725
- return this.data[key];
726
- }
727
-
728
- set(key: string, value: unknown): void {
729
- // Create new instance instead of mutating
730
- this.data = { ...this.data, [key]: value };
731
- this.version++;
732
- }
733
-
734
- getAll(): Record<string, unknown> {
735
- return { ...this.data }; // Return copy
736
- }
737
-
738
- setAll(values: Record<string, unknown>): void {
739
- this.data = { ...values };
740
- this.version++;
450
+ constructor(private client: RedisClient, private sessionId: string) {}
451
+
452
+ get<T>(): T | undefined {
453
+ // Implement Redis-backed context retrieval
741
454
  }
742
-
743
- getVersion(): number {
744
- return this.version;
455
+
456
+ set<T>(value: T): void {
457
+ // Implement Redis-backed context storage
745
458
  }
746
459
  }
747
460
 
748
- // Usage with custom context store
749
- cns.stimulate(input, payload, {
750
- createContextStore: () => new RedisContextStore(redisClient, 'session-123'),
751
- onTrace: (trace) => {
752
- // trace.contextStore is your custom RedisContextStore
753
- console.log('Using Redis-backed context');
754
- }
461
+ await cns.stimulate(signal, {
462
+ createContextStore: () => new RedisContextStore(redisClient, 'session-123')
755
463
  });
756
464
  ```
757
465
 
758
- **Context Store Interface**:
466
+ ### CNS Configuration
759
467
  ```typescript
760
- interface ICNSStimulationContextStore {
761
- get(key: string): unknown;
762
- set(key: string, value: unknown): void;
763
- getAll(): Record<string, unknown>;
764
- setAll(values: Record<string, unknown>): void;
765
- }
766
- ```
767
-
768
- **Custom Store Use Cases**:
769
- - **Persistence**: Redis, database, or file-based storage
770
- - **Distribution**: Share context across multiple CNS instances
771
- - **Immutability**: Version-controlled context for advanced debugging
772
- - **Encryption**: Secure sensitive context data
773
- - **Compression**: Optimize memory usage for large context objects
774
- - **Auditing**: Log all context changes for compliance
775
-
776
- ## ๐Ÿงช Testing
777
-
778
- ```bash
779
- npm test
780
- npm run test:types
781
- ```
782
-
783
- ## ๐Ÿ“ฆ Build
784
-
785
- ```bash
786
- npm run build
468
+ const cns = new CNS(neurons, {
469
+ autoCleanupContexts: true, // Auto-cleanup unused contexts
470
+ defaultConcurrency: 50 // Default concurrency limit
471
+ });
787
472
  ```
788
473
 
789
- ## ๐Ÿš€ Examples
790
-
791
- Run the examples to see CNS in action:
792
-
793
- ```bash
794
- npm run examples
795
- ```
474
+ โš ๏ธ **Performance Warning**: `autoCleanupContexts` adds computational overhead due to:
475
+ - **O(Vยฒ) initialization cost** - building SCC (Strongly Connected Components) structures
476
+ - **O(1 + A) runtime cost** per cleanup check (where A = number of SCC ancestors)
477
+ - **Memory overhead** for storing SCC graphs and ancestor relationships
796
478
 
797
- ## ๐Ÿค Contributing
479
+ **Use only when:**
480
+ - Memory leaks are a critical issue
481
+ - You have a small to medium-sized neuron graph (< 1000 neurons)
482
+ - Performance is less critical than memory management
798
483
 
799
- Contributions are welcome! Please feel free to submit a Pull Request.
484
+ **For production systems**, consider manual context cleanup or custom cleanup strategies instead.
800
485
 
801
- ## ๐Ÿ“„ License
486
+ ---
802
487
 
803
- MIT
488
+ *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.*