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