@cnstra/core 1.8.0 → 2.0.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.
Files changed (58) hide show
  1. package/README.md +17 -612
  2. package/dist/CNS.d.ts +6 -9
  3. package/dist/CNS.d.ts.map +1 -1
  4. package/dist/CNSCollateral.d.ts +3 -5
  5. package/dist/CNSCollateral.d.ts.map +1 -1
  6. package/dist/CNSInstanceNeuronQueue.d.ts +0 -1
  7. package/dist/CNSInstanceNeuronQueue.d.ts.map +1 -1
  8. package/dist/CNSNetwork.d.ts +16 -20
  9. package/dist/CNSNetwork.d.ts.map +1 -1
  10. package/dist/CNSNeuronActivationPump.d.ts +5 -5
  11. package/dist/CNSNeuronActivationPump.d.ts.map +1 -1
  12. package/dist/CNSStimulation.d.ts +16 -15
  13. package/dist/CNSStimulation.d.ts.map +1 -1
  14. package/dist/CNSStimulationContextStore.d.ts +6 -6
  15. package/dist/CNSStimulationContextStore.d.ts.map +1 -1
  16. package/dist/factory/index.d.ts +97 -14
  17. package/dist/factory/index.d.ts.map +1 -1
  18. package/dist/index.cjs +235 -197
  19. package/dist/index.cjs.map +1 -1
  20. package/dist/index.d.ts +4 -1
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +234 -198
  23. package/dist/index.js.map +1 -1
  24. package/dist/interfaces/ICNS.d.ts +5 -4
  25. package/dist/interfaces/ICNS.d.ts.map +1 -1
  26. package/dist/interfaces/ICNSCollateral.d.ts +3 -3
  27. package/dist/interfaces/ICNSCollateral.d.ts.map +1 -1
  28. package/dist/interfaces/ICNSStimulationContextStore.d.ts +5 -6
  29. package/dist/interfaces/ICNSStimulationContextStore.d.ts.map +1 -1
  30. package/dist/types/TCNSAfferentPath.d.ts +4 -0
  31. package/dist/types/TCNSAfferentPath.d.ts.map +1 -0
  32. package/dist/types/TCNSAxon.d.ts +23 -2
  33. package/dist/types/TCNSAxon.d.ts.map +1 -1
  34. package/dist/types/TCNSDendrite.d.ts +6 -6
  35. package/dist/types/TCNSDendrite.d.ts.map +1 -1
  36. package/dist/types/TCNSModality.d.ts +5 -0
  37. package/dist/types/TCNSModality.d.ts.map +1 -0
  38. package/dist/types/TCNSNeuron.d.ts +3 -3
  39. package/dist/types/TCNSNeuron.d.ts.map +1 -1
  40. package/dist/types/TCNSNeuronActivationTask.d.ts +5 -5
  41. package/dist/types/TCNSNeuronActivationTask.d.ts.map +1 -1
  42. package/dist/types/TCNSNeuronActivationTaskFailure.d.ts +2 -2
  43. package/dist/types/TCNSNeuronActivationTaskFailure.d.ts.map +1 -1
  44. package/dist/types/TCNSNeuronResponseReturn.d.ts +8 -2
  45. package/dist/types/TCNSNeuronResponseReturn.d.ts.map +1 -1
  46. package/dist/types/TCNSPersist.d.ts +16 -0
  47. package/dist/types/TCNSPersist.d.ts.map +1 -0
  48. package/dist/types/TCNSSignal.d.ts +4 -3
  49. package/dist/types/TCNSSignal.d.ts.map +1 -1
  50. package/dist/types/TCNSStimulationOptions.d.ts +9 -9
  51. package/dist/types/TCNSStimulationOptions.d.ts.map +1 -1
  52. package/dist/types/TCNSStimulationQueueItem.d.ts +4 -3
  53. package/dist/types/TCNSStimulationQueueItem.d.ts.map +1 -1
  54. package/dist/types/TCNSStimulationResponse.d.ts +10 -11
  55. package/dist/types/TCNSStimulationResponse.d.ts.map +1 -1
  56. package/dist/types/TCNSSubscriber.d.ts +1 -1
  57. package/dist/types/TCNSSubscriber.d.ts.map +1 -1
  58. package/package.json +1 -1
package/dist/index.cjs CHANGED
@@ -12,12 +12,11 @@ var CNSStimulationContextStore = class {
12
12
  this.ctx.set(key, value);
13
13
  }
14
14
  getAll() {
15
- return Object.fromEntries(this.ctx);
15
+ return new Map(this.ctx);
16
16
  }
17
17
  setAll(values) {
18
- for (const [key, value] of Object.entries(values)) {
19
- this.ctx.set(key, value);
20
- }
18
+ this.ctx.clear();
19
+ for (const [key, value] of values) this.ctx.set(key, value);
21
20
  }
22
21
  delete(key) {
23
22
  this.ctx.delete(key);
@@ -164,10 +163,7 @@ var CNSStimulation = class {
164
163
  * Track active SCCs: SCC index -> count of active neurons in that SCC
165
164
  */
166
165
  this.activeSccCounts = /* @__PURE__ */ new Map();
167
- this.ctx = new CNSStimulationContextStore(
168
- new Map(Object.entries(options?.contextValues ?? {}))
169
- );
170
- this.id = options?.stimulationId || Math.random().toString(36).slice(2);
166
+ this.ctx = options?.ctx ?? new CNSStimulationContextStore();
171
167
  this.queue = new CNSNeuronActivationPump(
172
168
  (neuronActivationTask) => this.executeActivationTask(neuronActivationTask),
173
169
  options?.concurrency,
@@ -188,7 +184,7 @@ var CNSStimulation = class {
188
184
  const queuedTasks = this.queue.getQueuedTasks();
189
185
  for (const task of [...queuedTasks]) {
190
186
  const alreadyTracked = this.failedTasks.some(
191
- (ft) => ft.task.neuronId === task.neuronId && ft.task.dendriteCollateralName === task.dendriteCollateralName && ft.task.stimulationId === task.stimulationId
187
+ (ft) => ft.task === task
192
188
  );
193
189
  if (!alreadyTracked) {
194
190
  this.failedTasks.push({
@@ -244,11 +240,7 @@ var CNSStimulation = class {
244
240
  */
245
241
  enqueueTasks(tasks) {
246
242
  for (const task of tasks) {
247
- const taskWithId = {
248
- ...task,
249
- stimulationId: this.id
250
- };
251
- this.queue.enqueue(taskWithId);
243
+ this.queue.enqueue(task);
252
244
  }
253
245
  this.tryResolveCompleted();
254
246
  }
@@ -261,22 +253,22 @@ var CNSStimulation = class {
261
253
  /**
262
254
  * Mark a neuron as active (being processed)
263
255
  */
264
- markNeuronActive(neuronId) {
256
+ markNeuronActive(neuron2) {
265
257
  if (!this.autoCleanupContextsEnabled) return;
266
- this.incrementSccCount(neuronId);
258
+ this.incrementSccCount(neuron2);
267
259
  }
268
260
  /**
269
261
  * Mark a neuron as inactive (finished processing)
270
262
  */
271
- markNeuronInactive(neuronId) {
263
+ markNeuronInactive(neuron2) {
272
264
  if (!this.autoCleanupContextsEnabled) return;
273
- this.decrementSccCount(neuronId);
265
+ this.decrementSccCount(neuron2);
274
266
  }
275
267
  /**
276
268
  * Increment the active count for the SCC containing this neuron
277
269
  */
278
- incrementSccCount(neuronId) {
279
- const sccIndex = this.cns.network.getSccIndexByNeuronName(neuronId);
270
+ incrementSccCount(neuron2) {
271
+ const sccIndex = this.cns.network.getSccIndexByNeuron(neuron2);
280
272
  if (sccIndex === void 0) return;
281
273
  const currentCount = this.activeSccCounts.get(sccIndex) || 0;
282
274
  this.activeSccCounts.set(sccIndex, currentCount + 1);
@@ -284,66 +276,65 @@ var CNSStimulation = class {
284
276
  /**
285
277
  * Decrement the active count for the SCC containing this neuron
286
278
  */
287
- decrementSccCount(neuronId) {
288
- const sccIndex = this.cns.network.getSccIndexByNeuronName(neuronId);
279
+ decrementSccCount(neuron2) {
280
+ const sccIndex = this.cns.network.getSccIndexByNeuron(neuron2);
289
281
  if (sccIndex === void 0) return;
290
282
  const currentCount = this.activeSccCounts.get(sccIndex) || 0;
291
283
  const nextCount = Math.max(0, currentCount - 1);
292
284
  this.activeSccCounts.set(sccIndex, nextCount);
293
285
  if (currentCount === 0) {
294
286
  console.warn(
295
- `[CNSStimulation] Attempting to decrement SCC count below 0 for neuron ${neuronId}`
287
+ `[CNSStimulation] Attempting to decrement SCC count below 0 for neuron`
296
288
  );
297
289
  }
298
290
  }
299
291
  /**
300
292
  * Check if a neuron can be guaranteed not to be visited again
301
293
  */
302
- canNeuronBeGuaranteedDone(neuronId) {
294
+ canNeuronBeGuaranteedDone(neuron2) {
303
295
  if (!this.autoCleanupContextsEnabled) return false;
304
296
  return this.cns.network.canNeuronBeGuaranteedDone(
305
- neuronId,
297
+ neuron2,
306
298
  this.activeSccCounts
307
299
  );
308
300
  }
309
301
  cleanupCtxIfNeeded(neuron2) {
310
- if (this.autoCleanupContextsEnabled && this.canNeuronBeGuaranteedDone(neuron2.name)) {
311
- this.ctx.delete(neuron2.name);
302
+ if (this.autoCleanupContextsEnabled && this.canNeuronBeGuaranteedDone(neuron2)) {
303
+ this.ctx.delete(neuron2);
312
304
  }
313
305
  }
314
306
  createSubscriberQueueItem(subscriber, inputSignal) {
315
307
  if (this.options?.maxNeuronHops) {
316
- if (this.nueronVisitMap?.get(subscriber.neuron.name) ?? 0 >= this.options?.maxNeuronHops) {
308
+ if ((this.nueronVisitMap?.get(subscriber.neuron) ?? 0) >= this.options?.maxNeuronHops) {
317
309
  throw new Error(
318
- `Max neuron hops reached for neuron "${subscriber.neuron.name}" when trying to enqueue subscriber "${subscriber.dendrite.collateral.name}" in stimulation "${this.id}"`
310
+ `Max neuron hops reached when trying to enqueue subscriber`
319
311
  );
320
312
  } else {
321
313
  this.nueronVisitMap?.set(
322
- subscriber.neuron.name,
323
- (this.nueronVisitMap?.get(subscriber.neuron.name) ?? 0) + 1
314
+ subscriber.neuron,
315
+ (this.nueronVisitMap?.get(subscriber.neuron) ?? 0) + 1
324
316
  );
325
317
  }
326
318
  }
327
319
  const neuronActivationTask = {
328
- stimulationId: this.id,
329
- neuronId: subscriber.neuron.name,
330
- dendriteCollateralName: subscriber.dendrite.collateral.name,
320
+ neuron: subscriber.neuron,
321
+ dendriteCollateral: subscriber.dendrite.collateral,
331
322
  input: inputSignal
332
323
  };
333
324
  return neuronActivationTask;
334
325
  }
335
326
  executeActivationTask(neuronActivationTask) {
336
327
  const subscribers = this.cns.network.getSubscribers(
337
- neuronActivationTask.dendriteCollateralName
328
+ neuronActivationTask.dendriteCollateral
338
329
  );
339
330
  const subscriber = subscribers.find(
340
- (s) => s.neuron.name === neuronActivationTask.neuronId
331
+ (s) => s.neuron === neuronActivationTask.neuron
341
332
  );
342
333
  if (!subscriber) {
343
334
  this.failedTasks.push({
344
335
  task: neuronActivationTask,
345
336
  error: new Error(
346
- `Subscriber not found for neuron "${neuronActivationTask.neuronId}" and collateral "${neuronActivationTask.dendriteCollateralName}"`
337
+ `Subscriber not found for activation task`
347
338
  ),
348
339
  aborted: false
349
340
  });
@@ -352,28 +343,25 @@ var CNSStimulation = class {
352
343
  }
353
344
  const neuron2 = subscriber.neuron;
354
345
  const dendrite = subscriber.dendrite;
355
- const inputSignal = neuronActivationTask.input ? {
356
- collateralName: neuronActivationTask.input.collateralName,
357
- payload: neuronActivationTask.input.payload
358
- } : void 0;
346
+ const inputSignal = neuronActivationTask.input;
359
347
  const starter = () => {
360
- this.markNeuronActive(neuron2.name);
348
+ this.markNeuronActive(neuron2);
361
349
  let response;
362
350
  try {
363
351
  response = dendrite.response(
364
352
  inputSignal?.payload,
365
353
  neuron2.axon,
366
354
  {
367
- get: () => this.ctx.get(neuron2.name),
368
- set: (value) => this.ctx.set(neuron2.name, value),
369
- delete: () => this.ctx.delete(neuron2.name),
355
+ get: () => this.ctx.get(neuron2),
356
+ set: (value) => this.ctx.set(neuron2, value),
357
+ delete: () => this.ctx.delete(neuron2),
370
358
  abortSignal: this.options?.abortSignal,
371
359
  cns: this.cns,
372
- stimulationId: this.id
360
+ stimulation: this
373
361
  }
374
362
  );
375
363
  } catch (error) {
376
- this.markNeuronInactive(neuron2.name);
364
+ this.markNeuronInactive(neuron2);
377
365
  const isAborted = this.options?.abortSignal?.aborted ?? false;
378
366
  this.failedTasks.push({
379
367
  task: neuronActivationTask,
@@ -381,6 +369,7 @@ var CNSStimulation = class {
381
369
  aborted: isAborted
382
370
  });
383
371
  this.processResponseOrResponses(
372
+ neuron2,
384
373
  inputSignal,
385
374
  void 0,
386
375
  error
@@ -393,9 +382,7 @@ var CNSStimulation = class {
393
382
  const timedPromise = maxDuration && maxDuration > 0 ? new Promise((resolve, reject) => {
394
383
  const t = setTimeout(() => {
395
384
  const err = new Error(
396
- `Neuron "${String(
397
- neuron2.name
398
- )}" exceeded maxDuration ${maxDuration}ms`
385
+ `Neuron exceeded maxDuration ${maxDuration}ms`
399
386
  );
400
387
  reject(err);
401
388
  }, maxDuration);
@@ -414,8 +401,9 @@ var CNSStimulation = class {
414
401
  return timedPromise.then(
415
402
  (signal) => {
416
403
  return () => {
417
- this.markNeuronInactive(neuron2.name);
404
+ this.markNeuronInactive(neuron2);
418
405
  return this.processResponseOrResponses(
406
+ neuron2,
419
407
  inputSignal,
420
408
  signal
421
409
  );
@@ -423,7 +411,7 @@ var CNSStimulation = class {
423
411
  },
424
412
  (error) => {
425
413
  return () => {
426
- this.markNeuronInactive(neuron2.name);
414
+ this.markNeuronInactive(neuron2);
427
415
  const isAborted = this.options?.abortSignal?.aborted ?? false;
428
416
  this.failedTasks.push({
429
417
  task: neuronActivationTask,
@@ -431,6 +419,7 @@ var CNSStimulation = class {
431
419
  aborted: isAborted
432
420
  });
433
421
  return this.processResponseOrResponses(
422
+ neuron2,
434
423
  inputSignal,
435
424
  void 0,
436
425
  error
@@ -440,8 +429,9 @@ var CNSStimulation = class {
440
429
  );
441
430
  } else {
442
431
  return () => {
443
- this.markNeuronInactive(neuron2.name);
432
+ this.markNeuronInactive(neuron2);
444
433
  return this.processResponseOrResponses(
434
+ neuron2,
445
435
  inputSignal,
446
436
  response
447
437
  );
@@ -450,28 +440,25 @@ var CNSStimulation = class {
450
440
  };
451
441
  return this.instanceNeuronQueue.run(neuron2, starter);
452
442
  }
453
- processResponseOrResponses(inputSignal, outputSignalOrSignals, error) {
443
+ processResponseOrResponses(emitter, inputSignal, outputSignalOrSignals, error) {
454
444
  if (Array.isArray(outputSignalOrSignals)) {
455
445
  if (outputSignalOrSignals.length === 0) {
456
- this.processResponse(inputSignal, void 0, error);
446
+ this.processResponse(emitter, inputSignal, void 0, error);
457
447
  return;
458
448
  }
459
449
  for (const signal of outputSignalOrSignals) {
460
- this.processResponse(inputSignal, signal, error);
450
+ this.processResponse(emitter, inputSignal, signal, error);
461
451
  }
462
452
  return;
463
453
  }
464
- this.processResponse(inputSignal, outputSignalOrSignals, error);
454
+ this.processResponse(emitter, inputSignal, outputSignalOrSignals, error);
465
455
  }
466
- processResponse(inputSignal, outputSignal, error) {
467
- const collateralName = outputSignal?.collateralName;
468
- const ownerNeuron = collateralName ? this.cns.network.getParentNeuronByCollateralName(collateralName) : void 0;
469
- const subscribers = collateralName ? this.cns.network.getSubscribers(collateralName) : [];
456
+ processResponse(emitter, inputSignal, outputSignal, error) {
457
+ const collateral2 = outputSignal?.collateral;
458
+ const subscribers = collateral2 ? this.cns.network.getSubscribers(collateral2) : [];
470
459
  const subscriberActivationTasks = [];
471
- if (collateralName && !error) {
472
- if (ownerNeuron) {
473
- this.cleanupCtxIfNeeded(ownerNeuron);
474
- }
460
+ if (collateral2 && !error) {
461
+ if (emitter) this.cleanupCtxIfNeeded(emitter);
475
462
  for (let i = 0; i < subscribers.length; i++) {
476
463
  const task = this.createSubscriberQueueItem(
477
464
  subscribers[i],
@@ -490,8 +477,7 @@ var CNSStimulation = class {
490
477
  contextValue: this.ctx.getAll(),
491
478
  queueLength: this.queue.length + this.scheduledCount,
492
479
  stimulation: this,
493
- stimulationId: this.id,
494
- hops: this.options?.maxNeuronHops && ownerNeuron ? this.nueronVisitMap?.get(ownerNeuron.name) ?? 0 : void 0,
480
+ hops: this.options?.maxNeuronHops && emitter ? this.nueronVisitMap?.get(emitter) ?? 0 : void 0,
495
481
  error
496
482
  });
497
483
  } catch (e) {
@@ -535,24 +521,10 @@ var CNSStimulation = class {
535
521
  }
536
522
  responseToSignal(signalOrSignals) {
537
523
  if (Array.isArray(signalOrSignals)) {
538
- for (const signal of signalOrSignals) {
539
- const ownerNeuron2 = this.cns.network.getParentNeuronByCollateralName(
540
- signal.collateralName
541
- );
542
- if (ownerNeuron2) {
543
- this.markNeuronActive(ownerNeuron2.name);
544
- }
545
- }
546
- this.processResponseOrResponses(void 0, signalOrSignals);
524
+ this.processResponseOrResponses(void 0, void 0, signalOrSignals);
547
525
  return;
548
526
  }
549
- const ownerNeuron = this.cns.network.getParentNeuronByCollateralName(
550
- signalOrSignals.collateralName
551
- );
552
- if (ownerNeuron) {
553
- this.markNeuronActive(ownerNeuron.name);
554
- }
555
- this.processResponse(void 0, signalOrSignals);
527
+ this.processResponse(void 0, void 0, signalOrSignals);
556
528
  this.tryResolveCompleted();
557
529
  }
558
530
  };
@@ -567,10 +539,10 @@ var CNSInstanceNeuronQueue = class {
567
539
  if (limit === void 0 || limit <= 0) {
568
540
  return fn();
569
541
  }
570
- let gate = this.gates.get(neuron2.name);
542
+ let gate = this.gates.get(neuron2);
571
543
  if (!gate) {
572
544
  gate = { limit, active: 0, waiters: [] };
573
- this.gates.set(neuron2.name, gate);
545
+ this.gates.set(neuron2, gate);
574
546
  } else {
575
547
  gate.limit = limit;
576
548
  }
@@ -579,7 +551,7 @@ var CNSInstanceNeuronQueue = class {
579
551
  try {
580
552
  cb();
581
553
  } finally {
582
- const g = this.gates.get(neuron2.name);
554
+ const g = this.gates.get(neuron2);
583
555
  g.active = Math.max(0, g.active - 1);
584
556
  if (g.waiters.length > 0) {
585
557
  const next = g.waiters.shift();
@@ -598,7 +570,7 @@ var CNSInstanceNeuronQueue = class {
598
570
  }
599
571
  return new Promise((resolve) => {
600
572
  const starter = () => {
601
- const g = this.gates.get(neuron2.name);
573
+ const g = this.gates.get(neuron2);
602
574
  g.active++;
603
575
  const ret = fn();
604
576
  if (ret && typeof ret.then === "function") {
@@ -618,11 +590,11 @@ var CNSNetwork = class {
618
590
  this.neurons = neurons;
619
591
  /**
620
592
  * Strongly Connected Components of the neuron graph.
621
- * Each component is a set of neuron IDs that can reach each other.
593
+ * Each component is a set of neuron objects that can reach each other.
622
594
  */
623
595
  this.stronglyConnectedComponents = [];
624
596
  /**
625
- * Quick lookup: neuron ID -> SCC index for fast reachability checks.
597
+ * Quick lookup: neuron -> SCC index for fast reachability checks.
626
598
  */
627
599
  this.neuronToSCC = /* @__PURE__ */ new Map();
628
600
  /**
@@ -634,68 +606,48 @@ var CNSNetwork = class {
634
606
  */
635
607
  this.sccAncestors = /* @__PURE__ */ new Map();
636
608
  /**
637
- * Fast lookup: collateralName -> list of (neuron, dendrite) subscribers.
609
+ * Fast lookup: collateral -> list of (neuron, dendrite) subscribers.
638
610
  * Built once at construction time.
639
611
  */
640
612
  this.subIndex = /* @__PURE__ */ new Map();
641
- this.neuronIndex = /* @__PURE__ */ new Map();
642
613
  this.dendriteIndex = /* @__PURE__ */ new Map();
643
- this.collateralIndex = /* @__PURE__ */ new Map();
644
- this.parentNeuronByCollateralName = /* @__PURE__ */ new Map();
614
+ this.collateralIndex = /* @__PURE__ */ new Set();
615
+ this.parentNeuronByCollateral = /* @__PURE__ */ new Map();
645
616
  this.validateUniqueIdentifiers();
646
617
  this.buildIndexes();
647
618
  this.buildSCC();
648
619
  }
649
620
  /**
650
621
  * Validate uniqueness constraints:
651
- * - neuron names are unique and non-empty
652
- * - axon collateral types are globally unique (single owner)
653
- * - the same collateral instance cannot be owned by multiple neurons
622
+ * - (legacy) collateral instances used to be single-owner; in object-identity routing
623
+ * a collateral can be emitted by multiple neurons (fan-in), so we no longer
624
+ * enforce single ownership here.
654
625
  * Throws aggregated error if violations are found.
655
626
  */
656
627
  validateUniqueIdentifiers() {
657
- const errors = [];
658
- const seenNeuronNames = /* @__PURE__ */ new Set();
659
- for (const neuron2 of this.neurons) {
660
- const name = neuron2?.name ?? "";
661
- if (!name || typeof name !== "string" || name.trim().length === 0) {
662
- errors.push(`Neuron has empty or invalid name: "${name}"`);
663
- } else if (seenNeuronNames.has(name)) {
664
- errors.push(`Duplicate neuron name: "${name}"`);
665
- } else {
666
- seenNeuronNames.add(name);
667
- }
668
- }
669
- if (errors.length > 0) {
670
- throw new Error(
671
- `[CNS] Topology uniqueness validation failed:
672
- - ` + errors.join(`
673
- - `)
674
- );
675
- }
676
628
  }
677
629
  /**
678
630
  * Build the neuron graph where neurons are connected if one can stimulate the other.
679
631
  */
680
632
  buildNeuronGraph() {
681
633
  const graph = /* @__PURE__ */ new Map();
682
- const neuronIds = this.neurons.map((n) => n.name);
683
- for (const neuronId of neuronIds) {
684
- graph.set(neuronId, /* @__PURE__ */ new Set());
634
+ for (const neuron2 of this.neurons) {
635
+ graph.set(neuron2, /* @__PURE__ */ new Set());
685
636
  }
686
637
  for (const neuron2 of this.neurons) {
687
638
  const reachableNeurons = /* @__PURE__ */ new Set();
688
639
  for (const axonKey in neuron2.axon) {
689
640
  const collateral2 = neuron2.axon[axonKey];
690
- const collateralName = collateral2.name;
691
- const subscribers = this.subIndex.get(collateralName);
641
+ const subscribers = this.subIndex.get(
642
+ collateral2
643
+ );
692
644
  if (subscribers) {
693
645
  for (const { neuron: targetNeuron } of subscribers) {
694
- reachableNeurons.add(targetNeuron.name);
646
+ reachableNeurons.add(targetNeuron);
695
647
  }
696
648
  }
697
649
  }
698
- graph.set(neuron2.name, reachableNeurons);
650
+ graph.set(neuron2, reachableNeurons);
699
651
  }
700
652
  return graph;
701
653
  }
@@ -704,48 +656,47 @@ var CNSNetwork = class {
704
656
  */
705
657
  buildSCC() {
706
658
  const graph = this.buildNeuronGraph();
707
- const neuronIds = this.neurons.map((n) => n.name);
708
659
  const index = /* @__PURE__ */ new Map();
709
660
  const lowlink = /* @__PURE__ */ new Map();
710
661
  const onStack = /* @__PURE__ */ new Set();
711
662
  const stack = [];
712
663
  const components = [];
713
664
  let currentIndex = 0;
714
- const strongConnect = (neuronId) => {
715
- index.set(neuronId, currentIndex);
716
- lowlink.set(neuronId, currentIndex);
665
+ const strongConnect = (neuron2) => {
666
+ index.set(neuron2, currentIndex);
667
+ lowlink.set(neuron2, currentIndex);
717
668
  currentIndex++;
718
- stack.push(neuronId);
719
- onStack.add(neuronId);
720
- const neighbors = graph.get(neuronId) || /* @__PURE__ */ new Set();
669
+ stack.push(neuron2);
670
+ onStack.add(neuron2);
671
+ const neighbors = graph.get(neuron2) || /* @__PURE__ */ new Set();
721
672
  for (const neighbor of Array.from(neighbors)) {
722
673
  if (!index.has(neighbor)) {
723
674
  strongConnect(neighbor);
724
675
  lowlink.set(
725
- neuronId,
726
- Math.min(lowlink.get(neuronId), lowlink.get(neighbor))
676
+ neuron2,
677
+ Math.min(lowlink.get(neuron2), lowlink.get(neighbor))
727
678
  );
728
679
  } else if (onStack.has(neighbor)) {
729
680
  lowlink.set(
730
- neuronId,
731
- Math.min(lowlink.get(neuronId), index.get(neighbor))
681
+ neuron2,
682
+ Math.min(lowlink.get(neuron2), index.get(neighbor))
732
683
  );
733
684
  }
734
685
  }
735
- if (lowlink.get(neuronId) === index.get(neuronId)) {
686
+ if (lowlink.get(neuron2) === index.get(neuron2)) {
736
687
  const component = /* @__PURE__ */ new Set();
737
688
  let w;
738
689
  do {
739
690
  w = stack.pop();
740
691
  onStack.delete(w);
741
692
  component.add(w);
742
- } while (w !== neuronId);
693
+ } while (w !== neuron2);
743
694
  components.push(component);
744
695
  }
745
696
  };
746
- for (const neuronId of neuronIds) {
747
- if (!index.has(neuronId)) {
748
- strongConnect(neuronId);
697
+ for (const neuron2 of this.neurons) {
698
+ if (!index.has(neuron2)) {
699
+ strongConnect(neuron2);
749
700
  }
750
701
  }
751
702
  this.stronglyConnectedComponents = components;
@@ -767,10 +718,10 @@ var CNSNetwork = class {
767
718
  }
768
719
  for (let i = 0; i < this.stronglyConnectedComponents.length; i++) {
769
720
  const scc = this.stronglyConnectedComponents[i];
770
- for (const neuronId of Array.from(scc)) {
771
- const neighbors = neuronGraph.get(neuronId) || /* @__PURE__ */ new Set();
772
- for (const neighborId of Array.from(neighbors)) {
773
- const neighborSccIndex = this.neuronToSCC.get(neighborId);
721
+ for (const neuron2 of Array.from(scc)) {
722
+ const neighbors = neuronGraph.get(neuron2) || /* @__PURE__ */ new Set();
723
+ for (const neighbor of Array.from(neighbors)) {
724
+ const neighborSccIndex = this.neuronToSCC.get(neighbor);
774
725
  if (neighborSccIndex !== void 0 && neighborSccIndex !== i) {
775
726
  this.sccDag.get(neighborSccIndex).add(i);
776
727
  }
@@ -831,23 +782,23 @@ var CNSNetwork = class {
831
782
  * Returns true if the neuron is in a strongly connected component with more than one member,
832
783
  * or if it's in a single-member SCC that can reach itself.
833
784
  */
834
- getSCCSetByNeuronName(neuronName) {
835
- const sccIndex = this.neuronToSCC.get(neuronName);
785
+ getSCCSetByNeuron(neuron2) {
786
+ const sccIndex = this.neuronToSCC.get(neuron2);
836
787
  if (sccIndex === void 0) return;
837
788
  return this.stronglyConnectedComponents[sccIndex];
838
789
  }
839
790
  /**
840
- * Get the SCC index for a given neuron ID
791
+ * Get the SCC index for a given neuron
841
792
  */
842
- getSccIndexByNeuronName(neuronName) {
843
- return this.neuronToSCC.get(neuronName);
793
+ getSccIndexByNeuron(neuron2) {
794
+ return this.neuronToSCC.get(neuron2);
844
795
  }
845
796
  /**
846
797
  * Check if a neuron can be guaranteed not to be visited again during the current propagation.
847
798
  * This is the core logic for safe context cleanup.
848
799
  */
849
- canNeuronBeGuaranteedDone(neuronName, activeSccCounts) {
850
- const sccIndex = this.neuronToSCC.get(neuronName);
800
+ canNeuronBeGuaranteedDone(neuron2, activeSccCounts) {
801
+ const sccIndex = this.neuronToSCC.get(neuron2);
851
802
  if (sccIndex === void 0) return true;
852
803
  if (activeSccCounts.get(sccIndex) && activeSccCounts.get(sccIndex) > 0) {
853
804
  return false;
@@ -863,11 +814,12 @@ var CNSNetwork = class {
863
814
  }
864
815
  buildIndexes() {
865
816
  this.subIndex.clear();
866
- this.parentNeuronByCollateralName.clear();
817
+ this.parentNeuronByCollateral.clear();
818
+ this.dendriteIndex.clear();
819
+ this.collateralIndex.clear();
867
820
  for (const neuron2 of this.neurons) {
868
- this.neuronIndex.set(neuron2.name, neuron2);
869
821
  for (const dendrite of neuron2.dendrites) {
870
- const key = dendrite.collateral.name;
822
+ const key = dendrite.collateral;
871
823
  const arr = this.subIndex.get(key) ?? [];
872
824
  arr.push({ neuron: neuron2, dendrite });
873
825
  this.subIndex.set(
@@ -877,19 +829,16 @@ var CNSNetwork = class {
877
829
  this.dendriteIndex.set(key, dendrite);
878
830
  }
879
831
  Object.values(neuron2.axon).forEach((collateral2) => {
880
- this.parentNeuronByCollateralName.set(
881
- collateral2.name,
832
+ this.parentNeuronByCollateral.set(
833
+ collateral2,
882
834
  neuron2
883
835
  );
884
- this.collateralIndex.set(
885
- collateral2.name,
886
- collateral2
887
- );
836
+ this.collateralIndex.add(collateral2);
888
837
  });
889
838
  }
890
839
  }
891
- getParentNeuronByCollateralName(collateralName) {
892
- return this.parentNeuronByCollateralName.get(collateralName);
840
+ getParentNeuronByCollateral(collateral2) {
841
+ return this.parentNeuronByCollateral.get(collateral2);
893
842
  }
894
843
  getDendrites() {
895
844
  return Array.from(this.dendriteIndex.values());
@@ -897,19 +846,8 @@ var CNSNetwork = class {
897
846
  getCollaterals() {
898
847
  return Array.from(this.collateralIndex.values());
899
848
  }
900
- getNeurons() {
901
- return Array.from(this.neuronIndex.values());
902
- }
903
- getCollateralByName(collateralName) {
904
- return this.collateralIndex.get(
905
- collateralName
906
- );
907
- }
908
- getNeuronByName(neuronName) {
909
- return this.neuronIndex.get(neuronName);
910
- }
911
- getSubscribers(collateralName) {
912
- return this.subIndex.get(collateralName) ?? [];
849
+ getSubscribers(collateral2) {
850
+ return this.subIndex.get(collateral2) ?? [];
913
851
  }
914
852
  };
915
853
 
@@ -918,10 +856,6 @@ var CNS = class {
918
856
  constructor(neurons, options) {
919
857
  this.neurons = neurons;
920
858
  this.options = options;
921
- /**
922
- * Global per-neuron concurrency gates shared across all stimulations.
923
- */
924
- this.neuronGates = /* @__PURE__ */ new Map();
925
859
  /**
926
860
  * Global task queue used by stimulation to schedule per-neuron gated work.
927
861
  * Does not replace per-stimulation concurrency; only coordinates global per-neuron limits.
@@ -987,10 +921,14 @@ var CNS = class {
987
921
  }
988
922
  stimulate(signalOrSignals, options) {
989
923
  const wrapped = this.wrapOnResponse(options?.onResponse);
990
- const stimulation = new CNSStimulation(this, this.instanceNeuronQueue, {
991
- ...options,
992
- onResponse: wrapped
993
- });
924
+ const stimulation = new CNSStimulation(
925
+ this,
926
+ this.instanceNeuronQueue,
927
+ {
928
+ ...options,
929
+ onResponse: wrapped
930
+ }
931
+ );
994
932
  stimulation.responseToSignal(signalOrSignals);
995
933
  return stimulation;
996
934
  }
@@ -999,10 +937,14 @@ var CNS = class {
999
937
  */
1000
938
  activate(tasks, options) {
1001
939
  const wrapped = this.wrapOnResponse(options?.onResponse);
1002
- const stimulation = new CNSStimulation(this, this.instanceNeuronQueue, {
1003
- ...options,
1004
- onResponse: wrapped
1005
- });
940
+ const stimulation = new CNSStimulation(
941
+ this,
942
+ this.instanceNeuronQueue,
943
+ {
944
+ ...options,
945
+ onResponse: wrapped
946
+ }
947
+ );
1006
948
  stimulation.enqueueTasks(tasks);
1007
949
  return stimulation;
1008
950
  }
@@ -1010,17 +952,14 @@ var CNS = class {
1010
952
 
1011
953
  // src/CNSCollateral.ts
1012
954
  var CNSCollateral = class {
1013
- constructor(name) {
1014
- this.name = name;
1015
- }
1016
955
  createSignal(payload) {
1017
- return { collateralName: this.name, payload };
956
+ return { collateral: this, payload };
1018
957
  }
1019
958
  };
1020
959
 
1021
960
  // src/factory/index.ts
1022
- var collateral = (name) => new CNSCollateral(name);
1023
- var neuron = (name, axon) => {
961
+ var collateral = () => new CNSCollateral();
962
+ var neuron = (axon) => {
1024
963
  const dendrites = [];
1025
964
  const api = {
1026
965
  setConcurrency: (n) => {
@@ -1045,29 +984,128 @@ var neuron = (name, axon) => {
1045
984
  return api;
1046
985
  }),
1047
986
  axon,
1048
- name,
1049
987
  concurrency: void 0,
1050
988
  maxDuration: void 0,
1051
989
  dendrites,
1052
990
  dendrite(s) {
1053
- dendrites.push(
1054
- s
1055
- );
991
+ if ("collateral" in s && "response" in s && Array.isArray(s.collateral)) {
992
+ for (const collateral2 of s.collateral) {
993
+ dendrites.push({
994
+ collateral: collateral2,
995
+ response: s.response
996
+ });
997
+ }
998
+ } else {
999
+ dendrites.push(
1000
+ s
1001
+ );
1002
+ }
1003
+ return api;
1004
+ },
1005
+ modalityDendrite(s) {
1006
+ const {
1007
+ collateral: collateral2,
1008
+ modality: singleModality,
1009
+ modalities: multipleModalities,
1010
+ afferentPaths: singleAfferentPaths,
1011
+ default: singleDefaultHandler,
1012
+ default: globalDefaultHandler,
1013
+ output
1014
+ } = s;
1015
+ const modalityConfigs = multipleModalities ? multipleModalities : singleModality ? [
1016
+ {
1017
+ modality: singleModality,
1018
+ afferentPaths: singleAfferentPaths,
1019
+ default: singleDefaultHandler
1020
+ }
1021
+ ] : [];
1022
+ const response = (payload, axon2, ctx) => {
1023
+ const stimulation = ctx.stimulation;
1024
+ const stimOptions = stimulation?.options;
1025
+ const runGlobalDefault = () => {
1026
+ if (!globalDefaultHandler) {
1027
+ throw new Error(
1028
+ `modalityDendrite: No handler found for modality and no default handler provided`
1029
+ );
1030
+ }
1031
+ const res = globalDefaultHandler(payload, axon2, ctx);
1032
+ if (res && typeof res.then === "function") {
1033
+ return res.then(
1034
+ (result) => output(result, axon2, ctx)
1035
+ );
1036
+ }
1037
+ return output(res, axon2, ctx);
1038
+ };
1039
+ if (!stimOptions || !stimOptions.modality) {
1040
+ return runGlobalDefault();
1041
+ }
1042
+ const matchingConfig = modalityConfigs.find(
1043
+ (config) => config.modality === stimOptions.modality
1044
+ );
1045
+ if (!matchingConfig) {
1046
+ return runGlobalDefault();
1047
+ }
1048
+ const stimAfferentPath = stimOptions.afferentPath;
1049
+ const handler = stimAfferentPath && matchingConfig.afferentPaths ? matchingConfig.afferentPaths.get(stimAfferentPath) : void 0;
1050
+ const effectiveHandler = handler ?? matchingConfig.default ?? globalDefaultHandler;
1051
+ if (!effectiveHandler) {
1052
+ throw new Error(
1053
+ `modalityDendrite: No handler found for afferent path in modality and no default handler provided`
1054
+ );
1055
+ }
1056
+ const maybeResult = effectiveHandler(payload, axon2, ctx);
1057
+ if (maybeResult && typeof maybeResult.then === "function") {
1058
+ return maybeResult.then(
1059
+ (result) => output(result, axon2, ctx)
1060
+ );
1061
+ }
1062
+ try {
1063
+ return output(maybeResult, axon2, ctx);
1064
+ } catch (error) {
1065
+ if (error instanceof Error && error.message.includes(
1066
+ "Cannot read properties of undefined"
1067
+ )) {
1068
+ const axonKeys = axon2 && typeof axon2 === "object" ? Object.keys(axon2) : "not an object";
1069
+ throw new Error(
1070
+ `modalityDendrite: axon.output is undefined. Axon type: ${typeof axon2}, Axon keys: ${Array.isArray(axonKeys) ? axonKeys.join(", ") : axonKeys}. Make sure the neuron was created with the correct axon structure. Original error: ${error.message}`
1071
+ );
1072
+ }
1073
+ throw error;
1074
+ }
1075
+ };
1076
+ dendrites.push({
1077
+ collateral: collateral2,
1078
+ response
1079
+ });
1056
1080
  return api;
1057
1081
  }
1058
1082
  };
1059
1083
  return api;
1060
1084
  };
1061
1085
  var withCtx = () => ({
1062
- neuron: (name, axon) => {
1063
- return neuron(name, axon);
1086
+ neuron: (axon) => {
1087
+ return neuron(axon);
1064
1088
  }
1065
1089
  });
1090
+ var afferentPath = (parentAfferentPath) => {
1091
+ return {
1092
+ ...parentAfferentPath !== void 0 && {
1093
+ parentAfferentPath
1094
+ }
1095
+ };
1096
+ };
1097
+ var modality = (afferentPaths) => {
1098
+ return {
1099
+ afferentPaths
1100
+ };
1101
+ };
1066
1102
 
1067
1103
  exports.CNS = CNS;
1068
1104
  exports.CNSCollateral = CNSCollateral;
1069
1105
  exports.CNSStimulationContextStore = CNSStimulationContextStore;
1106
+ exports.afferentPath = afferentPath;
1070
1107
  exports.collateral = collateral;
1108
+ exports.modality = modality;
1071
1109
  exports.neuron = neuron;
1072
1110
  exports.withCtx = withCtx;
1073
1111
  //# sourceMappingURL=index.cjs.map