@cnstra/core 1.1.0 → 1.2.0

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.
package/README.md CHANGED
@@ -7,6 +7,8 @@
7
7
  **CNStra (Central Nervous System Orchestrator)** models your app as a **typed neuron graph**.
8
8
  You explicitly start a run with `cns.stimulate(...)`; CNStra then performs a **deterministic, hop-bounded traversal** from **collateral → dendrite → returned signal**, step by step.
9
9
 
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
+
10
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.
11
13
 
12
14
  ## 🏗️ Core Model
@@ -44,8 +46,8 @@ const userService = neuron('user-service', {
44
46
  })
45
47
  .dendrite({
46
48
  collateral: userCreated,
47
- response: async (payload, axon) => {
48
- const userData = payload as { id: string; name: string };
49
+ response: (payload, axon) => {
50
+ const userData = payload;
49
51
 
50
52
  // Process the user creation
51
53
  console.log(`Processing user: ${userData.name}`);
@@ -119,7 +121,7 @@ new CNS(neurons)
119
121
 
120
122
  #### `stimulate()` Method
121
123
  ```typescript
122
- await cns.stimulate(collateral, payload, options?)
124
+ cns.stimulate(collateral, payload, options?)
123
125
  ```
124
126
 
125
127
  **Parameters:**
@@ -128,12 +130,333 @@ await cns.stimulate(collateral, payload, options?)
128
130
  - `options`: Optional configuration object
129
131
 
130
132
  **Options:**
131
- - `maxHops`: Maximum number of signal hops (default: 1000)
132
- - `onTrace`: Callback for tracing signal flow
133
- - `abortSignal`: AbortController signal for cancellation
133
+
134
+ ##### `maxHops?: number` (default: 1000)
135
+ Prevents infinite loops by limiting signal traversal depth. Each signal hop increments the counter.
136
+ ```typescript
137
+ // Prevent runaway recursion
138
+ cns.stimulate(input, data, {
139
+ maxHops: 50 // Stop after 50 signal hops
140
+ });
141
+ ```
142
+ ⚠️ **Memory Impact**: Higher values allow deeper graphs but increase memory usage.
143
+
144
+ ##### `onTrace?: (trace) => void`
145
+ Real-time callback for monitoring signal flow, errors, and completion. Essential for fire-and-forget pattern.
146
+ ```typescript
147
+ cns.stimulate(input, data, {
148
+ onTrace: (trace) => {
149
+ console.log(`Signal: ${trace.collateralId}, Hops: ${trace.hops}`);
150
+
151
+ if (trace.error) {
152
+ console.error('Processing failed:', trace.error);
153
+ }
154
+
155
+ if (trace.queueLength === 0) {
156
+ console.log('Stimulation completed');
157
+ }
158
+ }
159
+ });
160
+ ```
161
+
162
+ ##### `abortSignal?: AbortSignal`
163
+ Gracefully stop stimulation using AbortController. Already-running operations complete, but no new work starts.
164
+ ```typescript
165
+ const controller = new AbortController();
166
+
167
+ cns.stimulate(input, data, {
168
+ abortSignal: controller.signal
169
+ });
170
+
171
+ // Cancel after 5 seconds
172
+ setTimeout(() => controller.abort(), 5000);
173
+ ```
174
+
175
+ ##### `ctx?: ICNSStimulationContextStore`
176
+ Provide existing context store for recovery/retry scenarios. Restores neuron state from previous stimulation.
177
+ ```typescript
178
+ // Retry with preserved context
179
+ cns.stimulate(failedCollateral, payload, {
180
+ ctx: savedContextStore, // Restore previous state
181
+ onTrace: (trace) => { /* monitor retry */ }
182
+ });
183
+ ```
184
+
185
+ ##### `createContextStore?: () => ICNSStimulationContextStore`
186
+ Factory for custom context store implementations. Useful for persistence, encryption, or specialized storage.
187
+ ```typescript
188
+ cns.stimulate(input, data, {
189
+ createContextStore: () => new RedisContextStore('session-123'),
190
+ onTrace: (trace) => {
191
+ // trace.contextStore is your custom RedisContextStore
192
+ }
193
+ });
194
+ ```
195
+
196
+ ##### `spikeId?: string`
197
+ Custom identifier for this stimulation cascade. Auto-generated if not provided. Useful for debugging and correlation.
198
+ ```typescript
199
+ cns.stimulate(input, data, {
200
+ spikeId: 'user-action-' + Date.now(),
201
+ onTrace: (trace) => {
202
+ console.log('Spike ID:', trace.spikeId);
203
+ }
204
+ });
205
+ ```
206
+
207
+ ##### `allowType?: (collateralId: string) => boolean`
208
+ Filter which collateral types can be processed. Useful for selective stimulation or debugging specific paths.
209
+ ```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
+ }
215
+ });
216
+ ```
217
+
218
+ ##### `concurrency?: number` (default: unlimited)
219
+ Limit concurrent operations to prevent resource exhaustion. Both sync and async operations count toward this limit.
220
+ ```typescript
221
+ cns.stimulate(input, data, {
222
+ concurrency: 10, // Max 10 operations at once
223
+ onTrace: (trace) => {
224
+ // Processing respects concurrency limit
225
+ }
226
+ });
227
+ ```
228
+
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
262
+
263
+ ```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
+ }
272
+ }
273
+ });
274
+
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
+ ```
134
289
 
135
290
  ## 🔄 Key Behavior
136
291
 
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.
294
+
295
+ **Synchronous Path** - No async/await overhead:
296
+ ```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
+ });
307
+
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');
312
+
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
+ });
320
+
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
+ });
328
+
329
+ // This entire chain executes synchronously in one tick
330
+ cns.stimulate(chainStart, { value: 5 });
331
+ ```
332
+
333
+ **Mixed Sync/Async** - Async only where needed:
334
+ ```typescript
335
+ const mixedNeuron = neuron('mixed-processor', { output })
336
+ .dendrite({
337
+ collateral: input,
338
+ response: async (payload, axon) => {
339
+ // Synchronous preprocessing - runs immediately
340
+ const validated = validateInput(payload);
341
+ const transformed = transformData(validated);
342
+
343
+ // Only here do we enter async context
344
+ const result = await fetchFromAPI(transformed);
345
+
346
+ // Synchronous postprocessing
347
+ const formatted = formatResult(result);
348
+ return axon.output.createSignal({ formatted });
349
+ }
350
+ });
351
+ ```
352
+
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
358
+
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.
361
+
362
+ #### Complex Orchestration Example: Card & Deck Creation
363
+
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
+ });
397
+
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
+ });
411
+
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
+ });
425
+
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
+ }
447
+ }
448
+ });
449
+ });
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
+
137
460
  ### Only Returned Signals Are Processed
138
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.
139
462
 
@@ -160,7 +483,7 @@ Multiple neurons can listen to the same collateral:
160
483
  const emailService = neuron('email-service', { emailSent })
161
484
  .dendrite({
162
485
  collateral: userCreated,
163
- response: async (payload, axon) => {
486
+ response: (payload, axon) => {
164
487
  return axon.emailSent.createSignal({ to: 'user@example.com' });
165
488
  }
166
489
  });
@@ -168,7 +491,7 @@ const emailService = neuron('email-service', { emailSent })
168
491
  const notificationService = neuron('notification-service', { notificationSent })
169
492
  .dendrite({
170
493
  collateral: userCreated,
171
- response: async (payload, axon) => {
494
+ response: (payload, axon) => {
172
495
  return axon.notificationSent.createSignal({ message: 'User created' });
173
496
  }
174
497
  });
@@ -201,10 +524,10 @@ const router = neuron('router', {
201
524
  Neurons can maintain local state using the context parameter:
202
525
 
203
526
  ```typescript
204
- const statefulNeuron = neuron('stateful', { output: outputCollateral })
527
+ const statefulNeuron = withCtx().neuron('stateful', { output: outputCollateral })
205
528
  .dendrite({
206
529
  collateral: inputCollateral,
207
- response: async (payload, axon, ctx) => {
530
+ response: (payload, axon, ctx) => {
208
531
  // Get current state
209
532
  const currentState = ctx.get();
210
533
 
@@ -216,6 +539,240 @@ const statefulNeuron = neuron('stateful', { output: outputCollateral })
216
539
  });
217
540
  ```
218
541
 
542
+ ### Context-Based Retry Mechanisms
543
+
544
+ CNS supports robust retry mechanisms using context stores to maintain state across stimulation attempts. This is particularly useful for handling transient failures:
545
+
546
+ #### Stateful Retry Pattern
547
+ ```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;
558
+
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
+ }
622
+
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
+ });
637
+ }
638
+ }
639
+ });
640
+ };
641
+ ```
642
+
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
+
652
+ ```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
+ }
665
+ }
666
+ }
667
+ });
668
+
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
+ }
680
+ });
681
+ };
682
+ ```
683
+
684
+ **Custom Context Store Implementations**: You can create your own context store implementations by implementing the `ICNSStimulationContextStore` interface:
685
+
686
+ ```typescript
687
+ import { ICNSStimulationContextStore } from '@cnstra/core';
688
+
689
+ // Example: Redis-backed context store for distributed systems
690
+ 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++;
741
+ }
742
+
743
+ getVersion(): number {
744
+ return this.version;
745
+ }
746
+ }
747
+
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
+ }
755
+ });
756
+ ```
757
+
758
+ **Context Store Interface**:
759
+ ```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
+
219
776
  ## 🧪 Testing
220
777
 
221
778
  ```bash