@cnstra/core 2.2.1 → 2.3.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/dist/index.js CHANGED
@@ -23,6 +23,7 @@ var CNSStimulationContextStore = class {
23
23
  };
24
24
 
25
25
  // src/CNSStimulation.ts
26
+ var TASK_DENDRITE = /* @__PURE__ */ Symbol("cnstra.taskDendrite");
26
27
  var CNSDendriteContext = class {
27
28
  constructor(stimulation, neuron2, abortSignal, cns) {
28
29
  this.stimulation = stimulation;
@@ -73,7 +74,7 @@ var CNSStimulation = class {
73
74
  this.instanceNeuronQueue = instanceNeuronQueue;
74
75
  this.onResponse = onResponse;
75
76
  if (this.options?.maxNeuronHops) {
76
- this.nueronVisitMap = /* @__PURE__ */ new Map();
77
+ this.neuronVisitMap = /* @__PURE__ */ new Map();
77
78
  }
78
79
  if (this.autoCleanupContextsEnabled) {
79
80
  this.activeSccCounts = /* @__PURE__ */ new Map();
@@ -338,14 +339,14 @@ var CNSStimulation = class {
338
339
  }
339
340
  createSubscriberQueueItem(subscriber, inputSignal) {
340
341
  if (this.options?.maxNeuronHops) {
341
- if ((this.nueronVisitMap?.get(subscriber.neuron) ?? 0) >= this.options?.maxNeuronHops) {
342
+ if ((this.neuronVisitMap?.get(subscriber.neuron) ?? 0) >= this.options?.maxNeuronHops) {
342
343
  throw new Error(
343
344
  `Max neuron hops reached when trying to enqueue subscriber`
344
345
  );
345
346
  } else {
346
- this.nueronVisitMap?.set(
347
+ this.neuronVisitMap?.set(
347
348
  subscriber.neuron,
348
- (this.nueronVisitMap?.get(subscriber.neuron) ?? 0) + 1
349
+ (this.neuronVisitMap?.get(subscriber.neuron) ?? 0) + 1
349
350
  );
350
351
  }
351
352
  }
@@ -354,17 +355,20 @@ var CNSStimulation = class {
354
355
  dendriteCollateral: subscriber.dendrite.collateral,
355
356
  input: inputSignal
356
357
  };
358
+ neuronActivationTask[TASK_DENDRITE] = subscriber;
357
359
  return neuronActivationTask;
358
360
  }
359
361
  executeActivationTask(neuronActivationTask) {
360
- const subscribers = this.cns.network.getSubscribers(
361
- neuronActivationTask.dendriteCollateral
362
- );
363
- let subscriber;
364
- for (let i = 0; i < subscribers.length; i++) {
365
- if (subscribers[i].neuron === neuronActivationTask.neuron) {
366
- subscriber = subscribers[i];
367
- break;
362
+ let subscriber = neuronActivationTask[TASK_DENDRITE];
363
+ if (!subscriber) {
364
+ const subscribers = this.cns.network.getSubscribers(
365
+ neuronActivationTask.dendriteCollateral
366
+ );
367
+ for (let i = 0; i < subscribers.length; i++) {
368
+ if (subscribers[i].neuron === neuronActivationTask.neuron) {
369
+ subscriber = subscribers[i];
370
+ break;
371
+ }
368
372
  }
369
373
  }
370
374
  if (!subscriber) {
@@ -554,7 +558,7 @@ var CNSStimulation = class {
554
558
  inputSignal,
555
559
  outputSignal,
556
560
  this.qSize + this.scheduledCount,
557
- this.options?.maxNeuronHops && emitter ? this.nueronVisitMap?.get(emitter) ?? 0 : void 0,
561
+ this.options?.maxNeuronHops && emitter ? this.neuronVisitMap?.get(emitter) ?? 0 : void 0,
558
562
  error
559
563
  )
560
564
  );
@@ -669,8 +673,12 @@ var CNSNetwork = class {
669
673
  /**
670
674
  * Strongly Connected Components of the neuron graph.
671
675
  * Each component is a set of neuron objects that can reach each other.
676
+ *
677
+ * Computed lazily on first access: the SCC machinery is only needed for
678
+ * `autoCleanupContexts` (off by default), so most networks never pay for it.
672
679
  */
673
- this.stronglyConnectedComponents = [];
680
+ this._stronglyConnectedComponents = [];
681
+ this.sccBuilt = false;
674
682
  /**
675
683
  * Quick lookup: neuron -> SCC index for fast reachability checks.
676
684
  */
@@ -688,21 +696,18 @@ var CNSNetwork = class {
688
696
  * Built once at construction time.
689
697
  */
690
698
  this.subIndex = /* @__PURE__ */ new Map();
691
- this.dendriteIndex = /* @__PURE__ */ new Map();
692
- this.collateralIndex = /* @__PURE__ */ new Set();
693
699
  this.parentNeuronByCollateral = /* @__PURE__ */ new Map();
694
- this.validateUniqueIdentifiers();
695
700
  this.buildIndexes();
696
- this.buildSCC();
697
701
  }
698
- /**
699
- * Validate uniqueness constraints:
700
- * - (legacy) collateral instances used to be single-owner; in object-identity routing
701
- * a collateral can be emitted by multiple neurons (fan-in), so we no longer
702
- * enforce single ownership here.
703
- * Throws aggregated error if violations are found.
704
- */
705
- validateUniqueIdentifiers() {
702
+ get stronglyConnectedComponents() {
703
+ this.ensureSCC();
704
+ return this._stronglyConnectedComponents;
705
+ }
706
+ /** Build the SCC analysis the first time anything needs it. */
707
+ ensureSCC() {
708
+ if (this.sccBuilt) return;
709
+ this.sccBuilt = true;
710
+ this.buildSCC();
706
711
  }
707
712
  /**
708
713
  * Build the neuron graph where neurons are connected if one can stimulate the other.
@@ -777,7 +782,7 @@ var CNSNetwork = class {
777
782
  strongConnect(neuron2);
778
783
  }
779
784
  }
780
- this.stronglyConnectedComponents = components;
785
+ this._stronglyConnectedComponents = components;
781
786
  for (let i = 0; i < components.length; i++) {
782
787
  for (const neuronId of Array.from(components[i])) {
783
788
  this.neuronToSCC.set(neuronId, i);
@@ -791,11 +796,11 @@ var CNSNetwork = class {
791
796
  */
792
797
  buildSCCDAG(neuronGraph) {
793
798
  this.sccDag.clear();
794
- for (let i = 0; i < this.stronglyConnectedComponents.length; i++) {
799
+ for (let i = 0; i < this._stronglyConnectedComponents.length; i++) {
795
800
  this.sccDag.set(i, /* @__PURE__ */ new Set());
796
801
  }
797
- for (let i = 0; i < this.stronglyConnectedComponents.length; i++) {
798
- const scc = this.stronglyConnectedComponents[i];
802
+ for (let i = 0; i < this._stronglyConnectedComponents.length; i++) {
803
+ const scc = this._stronglyConnectedComponents[i];
799
804
  for (const neuron2 of Array.from(scc)) {
800
805
  const neighbors = neuronGraph.get(neuron2) || /* @__PURE__ */ new Set();
801
806
  for (const neighbor of Array.from(neighbors)) {
@@ -813,25 +818,27 @@ var CNSNetwork = class {
813
818
  */
814
819
  buildSCCAncestors() {
815
820
  this.sccAncestors.clear();
816
- for (let i = 0; i < this.stronglyConnectedComponents.length; i++) {
821
+ for (let i = 0; i < this._stronglyConnectedComponents.length; i++) {
817
822
  this.sccAncestors.set(i, /* @__PURE__ */ new Set());
818
823
  }
824
+ const n = this._stronglyConnectedComponents.length;
825
+ const forward = Array.from({ length: n }, () => []);
819
826
  const inDegree = /* @__PURE__ */ new Map();
820
827
  const queue = [];
821
- for (let i = 0; i < this.stronglyConnectedComponents.length; i++) {
822
- const incomingEdges = this.sccDag.get(i) || /* @__PURE__ */ new Set();
823
- inDegree.set(i, incomingEdges.size);
824
- if (incomingEdges.size === 0) {
825
- queue.push(i);
828
+ for (let target = 0; target < n; target++) {
829
+ const sources = this.sccDag.get(target) || /* @__PURE__ */ new Set();
830
+ inDegree.set(target, sources.size);
831
+ if (sources.size === 0) queue.push(target);
832
+ for (const source of Array.from(sources)) {
833
+ forward[source].push(target);
826
834
  }
827
835
  }
828
836
  while (queue.length > 0) {
829
837
  const current = queue.shift();
830
- const outgoingEdges = this.getOutgoingEdges(current);
831
- for (const neighbor of Array.from(outgoingEdges)) {
838
+ const currentAncestors = this.sccAncestors.get(current);
839
+ for (const neighbor of forward[current]) {
832
840
  const neighborAncestors = this.sccAncestors.get(neighbor);
833
841
  neighborAncestors.add(current);
834
- const currentAncestors = this.sccAncestors.get(current);
835
842
  for (const ancestor of Array.from(currentAncestors)) {
836
843
  neighborAncestors.add(ancestor);
837
844
  }
@@ -843,32 +850,22 @@ var CNSNetwork = class {
843
850
  }
844
851
  }
845
852
  }
846
- /**
847
- * Get outgoing edges for a given SCC index
848
- */
849
- getOutgoingEdges(sccIndex) {
850
- const outgoing = /* @__PURE__ */ new Set();
851
- for (const [targetScc, incomingEdges] of Array.from(this.sccDag)) {
852
- if (incomingEdges.has(sccIndex)) {
853
- outgoing.add(targetScc);
854
- }
855
- }
856
- return outgoing;
857
- }
858
853
  /**
859
854
  * Check if a neuron can be reached again (including self-calling).
860
855
  * Returns true if the neuron is in a strongly connected component with more than one member,
861
856
  * or if it's in a single-member SCC that can reach itself.
862
857
  */
863
858
  getSCCSetByNeuron(neuron2) {
859
+ this.ensureSCC();
864
860
  const sccIndex = this.neuronToSCC.get(neuron2);
865
861
  if (sccIndex === void 0) return;
866
- return this.stronglyConnectedComponents[sccIndex];
862
+ return this._stronglyConnectedComponents[sccIndex];
867
863
  }
868
864
  /**
869
865
  * Get the SCC index for a given neuron
870
866
  */
871
867
  getSccIndexByNeuron(neuron2) {
868
+ this.ensureSCC();
872
869
  return this.neuronToSCC.get(neuron2);
873
870
  }
874
871
  /**
@@ -876,6 +873,7 @@ var CNSNetwork = class {
876
873
  * This is the core logic for safe context cleanup.
877
874
  */
878
875
  canNeuronBeGuaranteedDone(neuron2, activeSccCounts) {
876
+ this.ensureSCC();
879
877
  const sccIndex = this.neuronToSCC.get(neuron2);
880
878
  if (sccIndex === void 0) return true;
881
879
  if (activeSccCounts.get(sccIndex) && activeSccCounts.get(sccIndex) > 0) {
@@ -893,8 +891,6 @@ var CNSNetwork = class {
893
891
  buildIndexes() {
894
892
  this.subIndex.clear();
895
893
  this.parentNeuronByCollateral.clear();
896
- this.dendriteIndex.clear();
897
- this.collateralIndex.clear();
898
894
  for (const neuron2 of this.neurons) {
899
895
  for (const dendrite of neuron2.dendrites) {
900
896
  const key = dendrite.collateral;
@@ -904,25 +900,40 @@ var CNSNetwork = class {
904
900
  key,
905
901
  arr
906
902
  );
907
- this.dendriteIndex.set(key, dendrite);
908
903
  }
909
904
  Object.values(neuron2.axon).forEach((collateral2) => {
910
905
  this.parentNeuronByCollateral.set(
911
906
  collateral2,
912
907
  neuron2
913
908
  );
914
- this.collateralIndex.add(collateral2);
915
909
  });
916
910
  }
917
911
  }
918
912
  getParentNeuronByCollateral(collateral2) {
919
913
  return this.parentNeuronByCollateral.get(collateral2);
920
914
  }
915
+ // Computed on demand (rarely used): one dendrite per collateral, last wins —
916
+ // preserving the previous index semantics without building it every construct.
921
917
  getDendrites() {
922
- return Array.from(this.dendriteIndex.values());
918
+ const byCollateral = /* @__PURE__ */ new Map();
919
+ for (const neuron2 of this.neurons) {
920
+ for (const dendrite of neuron2.dendrites) {
921
+ byCollateral.set(
922
+ dendrite.collateral,
923
+ dendrite
924
+ );
925
+ }
926
+ }
927
+ return Array.from(byCollateral.values());
923
928
  }
924
929
  getCollaterals() {
925
- return Array.from(this.collateralIndex.values());
930
+ const collaterals = /* @__PURE__ */ new Set();
931
+ for (const neuron2 of this.neurons) {
932
+ for (const collateral2 of Object.values(neuron2.axon)) {
933
+ collaterals.add(collateral2);
934
+ }
935
+ }
936
+ return Array.from(collaterals);
926
937
  }
927
938
  getSubscribers(collateral2) {
928
939
  return this.subIndex.get(collateral2) ?? [];
@@ -1196,27 +1207,26 @@ var neuron = (axon) => {
1196
1207
  modality: singleModality,
1197
1208
  modalities: multipleModalities,
1198
1209
  afferentPaths: singleAfferentPaths,
1199
- default: singleDefaultHandler,
1200
- default: globalDefaultHandler,
1210
+ default: defaultHandler,
1201
1211
  output
1202
1212
  } = s;
1203
1213
  const modalityConfigs = multipleModalities ? multipleModalities : singleModality ? [
1204
1214
  {
1205
1215
  modality: singleModality,
1206
1216
  afferentPaths: singleAfferentPaths,
1207
- default: singleDefaultHandler
1217
+ default: defaultHandler
1208
1218
  }
1209
1219
  ] : [];
1210
1220
  const response = (payload, axon2, ctx) => {
1211
1221
  const stimulation = ctx.stimulation;
1212
1222
  const stimOptions = stimulation?.options;
1213
1223
  const runGlobalDefault = () => {
1214
- if (!globalDefaultHandler) {
1224
+ if (!defaultHandler) {
1215
1225
  throw new Error(
1216
1226
  `modalityDendrite: No handler found for modality and no default handler provided`
1217
1227
  );
1218
1228
  }
1219
- const res = globalDefaultHandler(payload, axon2, ctx);
1229
+ const res = defaultHandler(payload, axon2, ctx);
1220
1230
  if (res && typeof res.then === "function") {
1221
1231
  return res.then(
1222
1232
  (result) => output(result, axon2, ctx)
@@ -1235,7 +1245,7 @@ var neuron = (axon) => {
1235
1245
  }
1236
1246
  const stimAfferentPath = stimOptions.afferentPath;
1237
1247
  const handler = stimAfferentPath && matchingConfig.afferentPaths ? matchingConfig.afferentPaths.get(stimAfferentPath) : void 0;
1238
- const effectiveHandler = handler ?? matchingConfig.default ?? globalDefaultHandler;
1248
+ const effectiveHandler = handler ?? matchingConfig.default ?? defaultHandler;
1239
1249
  if (!effectiveHandler) {
1240
1250
  throw new Error(
1241
1251
  `modalityDendrite: No handler found for afferent path in modality and no default handler provided`