@cnstra/core 2.2.1 → 2.4.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 +28 -6
- package/dist/CNS.d.ts +7 -1
- package/dist/CNS.d.ts.map +1 -1
- package/dist/CNSNetwork.d.ts +8 -15
- package/dist/CNSNetwork.d.ts.map +1 -1
- package/dist/CNSStimulation.d.ts +1 -1
- package/dist/CNSStimulation.d.ts.map +1 -1
- package/dist/factory/index.d.ts +94 -67
- package/dist/factory/index.d.ts.map +1 -1
- package/dist/index.cjs +103 -70
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +101 -71
- package/dist/index.js.map +1 -1
- package/dist/types/TCNSDendrite.d.ts +3 -8
- package/dist/types/TCNSDendrite.d.ts.map +1 -1
- package/dist/types/TCNSHandlerContext.d.ts +24 -0
- package/dist/types/TCNSHandlerContext.d.ts.map +1 -0
- package/dist/types/TCNSNeuron.d.ts +1 -1
- package/dist/types/TCNSNeuron.d.ts.map +1 -1
- package/package.json +1 -1
- package/dist/CNSTaskQueue.d.ts +0 -12
- package/dist/CNSTaskQueue.d.ts.map +0 -1
package/dist/index.cjs
CHANGED
|
@@ -25,6 +25,7 @@ var CNSStimulationContextStore = class {
|
|
|
25
25
|
};
|
|
26
26
|
|
|
27
27
|
// src/CNSStimulation.ts
|
|
28
|
+
var TASK_DENDRITE = /* @__PURE__ */ Symbol("cnstra.taskDendrite");
|
|
28
29
|
var CNSDendriteContext = class {
|
|
29
30
|
constructor(stimulation, neuron2, abortSignal, cns) {
|
|
30
31
|
this.stimulation = stimulation;
|
|
@@ -32,6 +33,12 @@ var CNSDendriteContext = class {
|
|
|
32
33
|
this.abortSignal = abortSignal;
|
|
33
34
|
this.cns = cns;
|
|
34
35
|
}
|
|
36
|
+
// Prototype getter, not a constructor field: the per-activation allocation is
|
|
37
|
+
// unchanged (still 4 fields) and you only pay a single `cns.global` read on the
|
|
38
|
+
// dendrites that actually reach for it. Value getter, so `{ global }` destructuring is safe.
|
|
39
|
+
get global() {
|
|
40
|
+
return this.cns.global;
|
|
41
|
+
}
|
|
35
42
|
get() {
|
|
36
43
|
return this.stimulation.getContext().get(this.neuron);
|
|
37
44
|
}
|
|
@@ -75,7 +82,7 @@ var CNSStimulation = class {
|
|
|
75
82
|
this.instanceNeuronQueue = instanceNeuronQueue;
|
|
76
83
|
this.onResponse = onResponse;
|
|
77
84
|
if (this.options?.maxNeuronHops) {
|
|
78
|
-
this.
|
|
85
|
+
this.neuronVisitMap = /* @__PURE__ */ new Map();
|
|
79
86
|
}
|
|
80
87
|
if (this.autoCleanupContextsEnabled) {
|
|
81
88
|
this.activeSccCounts = /* @__PURE__ */ new Map();
|
|
@@ -340,14 +347,14 @@ var CNSStimulation = class {
|
|
|
340
347
|
}
|
|
341
348
|
createSubscriberQueueItem(subscriber, inputSignal) {
|
|
342
349
|
if (this.options?.maxNeuronHops) {
|
|
343
|
-
if ((this.
|
|
350
|
+
if ((this.neuronVisitMap?.get(subscriber.neuron) ?? 0) >= this.options?.maxNeuronHops) {
|
|
344
351
|
throw new Error(
|
|
345
352
|
`Max neuron hops reached when trying to enqueue subscriber`
|
|
346
353
|
);
|
|
347
354
|
} else {
|
|
348
|
-
this.
|
|
355
|
+
this.neuronVisitMap?.set(
|
|
349
356
|
subscriber.neuron,
|
|
350
|
-
(this.
|
|
357
|
+
(this.neuronVisitMap?.get(subscriber.neuron) ?? 0) + 1
|
|
351
358
|
);
|
|
352
359
|
}
|
|
353
360
|
}
|
|
@@ -356,17 +363,20 @@ var CNSStimulation = class {
|
|
|
356
363
|
dendriteCollateral: subscriber.dendrite.collateral,
|
|
357
364
|
input: inputSignal
|
|
358
365
|
};
|
|
366
|
+
neuronActivationTask[TASK_DENDRITE] = subscriber;
|
|
359
367
|
return neuronActivationTask;
|
|
360
368
|
}
|
|
361
369
|
executeActivationTask(neuronActivationTask) {
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
+
let subscriber = neuronActivationTask[TASK_DENDRITE];
|
|
371
|
+
if (!subscriber) {
|
|
372
|
+
const subscribers = this.cns.network.getSubscribers(
|
|
373
|
+
neuronActivationTask.dendriteCollateral
|
|
374
|
+
);
|
|
375
|
+
for (let i = 0; i < subscribers.length; i++) {
|
|
376
|
+
if (subscribers[i].neuron === neuronActivationTask.neuron) {
|
|
377
|
+
subscriber = subscribers[i];
|
|
378
|
+
break;
|
|
379
|
+
}
|
|
370
380
|
}
|
|
371
381
|
}
|
|
372
382
|
if (!subscriber) {
|
|
@@ -556,7 +566,7 @@ var CNSStimulation = class {
|
|
|
556
566
|
inputSignal,
|
|
557
567
|
outputSignal,
|
|
558
568
|
this.qSize + this.scheduledCount,
|
|
559
|
-
this.options?.maxNeuronHops && emitter ? this.
|
|
569
|
+
this.options?.maxNeuronHops && emitter ? this.neuronVisitMap?.get(emitter) ?? 0 : void 0,
|
|
560
570
|
error
|
|
561
571
|
)
|
|
562
572
|
);
|
|
@@ -671,8 +681,12 @@ var CNSNetwork = class {
|
|
|
671
681
|
/**
|
|
672
682
|
* Strongly Connected Components of the neuron graph.
|
|
673
683
|
* Each component is a set of neuron objects that can reach each other.
|
|
684
|
+
*
|
|
685
|
+
* Computed lazily on first access: the SCC machinery is only needed for
|
|
686
|
+
* `autoCleanupContexts` (off by default), so most networks never pay for it.
|
|
674
687
|
*/
|
|
675
|
-
this.
|
|
688
|
+
this._stronglyConnectedComponents = [];
|
|
689
|
+
this.sccBuilt = false;
|
|
676
690
|
/**
|
|
677
691
|
* Quick lookup: neuron -> SCC index for fast reachability checks.
|
|
678
692
|
*/
|
|
@@ -690,21 +704,18 @@ var CNSNetwork = class {
|
|
|
690
704
|
* Built once at construction time.
|
|
691
705
|
*/
|
|
692
706
|
this.subIndex = /* @__PURE__ */ new Map();
|
|
693
|
-
this.dendriteIndex = /* @__PURE__ */ new Map();
|
|
694
|
-
this.collateralIndex = /* @__PURE__ */ new Set();
|
|
695
707
|
this.parentNeuronByCollateral = /* @__PURE__ */ new Map();
|
|
696
|
-
this.validateUniqueIdentifiers();
|
|
697
708
|
this.buildIndexes();
|
|
698
|
-
this.buildSCC();
|
|
699
709
|
}
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
710
|
+
get stronglyConnectedComponents() {
|
|
711
|
+
this.ensureSCC();
|
|
712
|
+
return this._stronglyConnectedComponents;
|
|
713
|
+
}
|
|
714
|
+
/** Build the SCC analysis the first time anything needs it. */
|
|
715
|
+
ensureSCC() {
|
|
716
|
+
if (this.sccBuilt) return;
|
|
717
|
+
this.sccBuilt = true;
|
|
718
|
+
this.buildSCC();
|
|
708
719
|
}
|
|
709
720
|
/**
|
|
710
721
|
* Build the neuron graph where neurons are connected if one can stimulate the other.
|
|
@@ -779,7 +790,7 @@ var CNSNetwork = class {
|
|
|
779
790
|
strongConnect(neuron2);
|
|
780
791
|
}
|
|
781
792
|
}
|
|
782
|
-
this.
|
|
793
|
+
this._stronglyConnectedComponents = components;
|
|
783
794
|
for (let i = 0; i < components.length; i++) {
|
|
784
795
|
for (const neuronId of Array.from(components[i])) {
|
|
785
796
|
this.neuronToSCC.set(neuronId, i);
|
|
@@ -793,11 +804,11 @@ var CNSNetwork = class {
|
|
|
793
804
|
*/
|
|
794
805
|
buildSCCDAG(neuronGraph) {
|
|
795
806
|
this.sccDag.clear();
|
|
796
|
-
for (let i = 0; i < this.
|
|
807
|
+
for (let i = 0; i < this._stronglyConnectedComponents.length; i++) {
|
|
797
808
|
this.sccDag.set(i, /* @__PURE__ */ new Set());
|
|
798
809
|
}
|
|
799
|
-
for (let i = 0; i < this.
|
|
800
|
-
const scc = this.
|
|
810
|
+
for (let i = 0; i < this._stronglyConnectedComponents.length; i++) {
|
|
811
|
+
const scc = this._stronglyConnectedComponents[i];
|
|
801
812
|
for (const neuron2 of Array.from(scc)) {
|
|
802
813
|
const neighbors = neuronGraph.get(neuron2) || /* @__PURE__ */ new Set();
|
|
803
814
|
for (const neighbor of Array.from(neighbors)) {
|
|
@@ -815,25 +826,27 @@ var CNSNetwork = class {
|
|
|
815
826
|
*/
|
|
816
827
|
buildSCCAncestors() {
|
|
817
828
|
this.sccAncestors.clear();
|
|
818
|
-
for (let i = 0; i < this.
|
|
829
|
+
for (let i = 0; i < this._stronglyConnectedComponents.length; i++) {
|
|
819
830
|
this.sccAncestors.set(i, /* @__PURE__ */ new Set());
|
|
820
831
|
}
|
|
832
|
+
const n = this._stronglyConnectedComponents.length;
|
|
833
|
+
const forward = Array.from({ length: n }, () => []);
|
|
821
834
|
const inDegree = /* @__PURE__ */ new Map();
|
|
822
835
|
const queue = [];
|
|
823
|
-
for (let
|
|
824
|
-
const
|
|
825
|
-
inDegree.set(
|
|
826
|
-
if (
|
|
827
|
-
|
|
836
|
+
for (let target = 0; target < n; target++) {
|
|
837
|
+
const sources = this.sccDag.get(target) || /* @__PURE__ */ new Set();
|
|
838
|
+
inDegree.set(target, sources.size);
|
|
839
|
+
if (sources.size === 0) queue.push(target);
|
|
840
|
+
for (const source of Array.from(sources)) {
|
|
841
|
+
forward[source].push(target);
|
|
828
842
|
}
|
|
829
843
|
}
|
|
830
844
|
while (queue.length > 0) {
|
|
831
845
|
const current = queue.shift();
|
|
832
|
-
const
|
|
833
|
-
for (const neighbor of
|
|
846
|
+
const currentAncestors = this.sccAncestors.get(current);
|
|
847
|
+
for (const neighbor of forward[current]) {
|
|
834
848
|
const neighborAncestors = this.sccAncestors.get(neighbor);
|
|
835
849
|
neighborAncestors.add(current);
|
|
836
|
-
const currentAncestors = this.sccAncestors.get(current);
|
|
837
850
|
for (const ancestor of Array.from(currentAncestors)) {
|
|
838
851
|
neighborAncestors.add(ancestor);
|
|
839
852
|
}
|
|
@@ -845,32 +858,22 @@ var CNSNetwork = class {
|
|
|
845
858
|
}
|
|
846
859
|
}
|
|
847
860
|
}
|
|
848
|
-
/**
|
|
849
|
-
* Get outgoing edges for a given SCC index
|
|
850
|
-
*/
|
|
851
|
-
getOutgoingEdges(sccIndex) {
|
|
852
|
-
const outgoing = /* @__PURE__ */ new Set();
|
|
853
|
-
for (const [targetScc, incomingEdges] of Array.from(this.sccDag)) {
|
|
854
|
-
if (incomingEdges.has(sccIndex)) {
|
|
855
|
-
outgoing.add(targetScc);
|
|
856
|
-
}
|
|
857
|
-
}
|
|
858
|
-
return outgoing;
|
|
859
|
-
}
|
|
860
861
|
/**
|
|
861
862
|
* Check if a neuron can be reached again (including self-calling).
|
|
862
863
|
* Returns true if the neuron is in a strongly connected component with more than one member,
|
|
863
864
|
* or if it's in a single-member SCC that can reach itself.
|
|
864
865
|
*/
|
|
865
866
|
getSCCSetByNeuron(neuron2) {
|
|
867
|
+
this.ensureSCC();
|
|
866
868
|
const sccIndex = this.neuronToSCC.get(neuron2);
|
|
867
869
|
if (sccIndex === void 0) return;
|
|
868
|
-
return this.
|
|
870
|
+
return this._stronglyConnectedComponents[sccIndex];
|
|
869
871
|
}
|
|
870
872
|
/**
|
|
871
873
|
* Get the SCC index for a given neuron
|
|
872
874
|
*/
|
|
873
875
|
getSccIndexByNeuron(neuron2) {
|
|
876
|
+
this.ensureSCC();
|
|
874
877
|
return this.neuronToSCC.get(neuron2);
|
|
875
878
|
}
|
|
876
879
|
/**
|
|
@@ -878,6 +881,7 @@ var CNSNetwork = class {
|
|
|
878
881
|
* This is the core logic for safe context cleanup.
|
|
879
882
|
*/
|
|
880
883
|
canNeuronBeGuaranteedDone(neuron2, activeSccCounts) {
|
|
884
|
+
this.ensureSCC();
|
|
881
885
|
const sccIndex = this.neuronToSCC.get(neuron2);
|
|
882
886
|
if (sccIndex === void 0) return true;
|
|
883
887
|
if (activeSccCounts.get(sccIndex) && activeSccCounts.get(sccIndex) > 0) {
|
|
@@ -895,8 +899,6 @@ var CNSNetwork = class {
|
|
|
895
899
|
buildIndexes() {
|
|
896
900
|
this.subIndex.clear();
|
|
897
901
|
this.parentNeuronByCollateral.clear();
|
|
898
|
-
this.dendriteIndex.clear();
|
|
899
|
-
this.collateralIndex.clear();
|
|
900
902
|
for (const neuron2 of this.neurons) {
|
|
901
903
|
for (const dendrite of neuron2.dendrites) {
|
|
902
904
|
const key = dendrite.collateral;
|
|
@@ -906,25 +908,40 @@ var CNSNetwork = class {
|
|
|
906
908
|
key,
|
|
907
909
|
arr
|
|
908
910
|
);
|
|
909
|
-
this.dendriteIndex.set(key, dendrite);
|
|
910
911
|
}
|
|
911
912
|
Object.values(neuron2.axon).forEach((collateral2) => {
|
|
912
913
|
this.parentNeuronByCollateral.set(
|
|
913
914
|
collateral2,
|
|
914
915
|
neuron2
|
|
915
916
|
);
|
|
916
|
-
this.collateralIndex.add(collateral2);
|
|
917
917
|
});
|
|
918
918
|
}
|
|
919
919
|
}
|
|
920
920
|
getParentNeuronByCollateral(collateral2) {
|
|
921
921
|
return this.parentNeuronByCollateral.get(collateral2);
|
|
922
922
|
}
|
|
923
|
+
// Computed on demand (rarely used): one dendrite per collateral, last wins —
|
|
924
|
+
// preserving the previous index semantics without building it every construct.
|
|
923
925
|
getDendrites() {
|
|
924
|
-
|
|
926
|
+
const byCollateral = /* @__PURE__ */ new Map();
|
|
927
|
+
for (const neuron2 of this.neurons) {
|
|
928
|
+
for (const dendrite of neuron2.dendrites) {
|
|
929
|
+
byCollateral.set(
|
|
930
|
+
dendrite.collateral,
|
|
931
|
+
dendrite
|
|
932
|
+
);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
return Array.from(byCollateral.values());
|
|
925
936
|
}
|
|
926
937
|
getCollaterals() {
|
|
927
|
-
|
|
938
|
+
const collaterals = /* @__PURE__ */ new Set();
|
|
939
|
+
for (const neuron2 of this.neurons) {
|
|
940
|
+
for (const collateral2 of Object.values(neuron2.axon)) {
|
|
941
|
+
collaterals.add(collateral2);
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
return Array.from(collaterals);
|
|
928
945
|
}
|
|
929
946
|
getSubscribers(collateral2) {
|
|
930
947
|
return this.subIndex.get(collateral2) ?? [];
|
|
@@ -933,7 +950,7 @@ var CNSNetwork = class {
|
|
|
933
950
|
|
|
934
951
|
// src/CNS.ts
|
|
935
952
|
var CNS = class {
|
|
936
|
-
constructor(neurons, options) {
|
|
953
|
+
constructor(neurons, options, global) {
|
|
937
954
|
this.neurons = neurons;
|
|
938
955
|
this.options = options;
|
|
939
956
|
/**
|
|
@@ -946,6 +963,7 @@ var CNS = class {
|
|
|
946
963
|
*/
|
|
947
964
|
this.globalResponseListeners = [];
|
|
948
965
|
this.network = new CNSNetwork(this.neurons);
|
|
966
|
+
this.global = global;
|
|
949
967
|
}
|
|
950
968
|
addResponseListener(listener) {
|
|
951
969
|
this.globalResponseListeners.push(listener);
|
|
@@ -1198,27 +1216,26 @@ var neuron = (axon) => {
|
|
|
1198
1216
|
modality: singleModality,
|
|
1199
1217
|
modalities: multipleModalities,
|
|
1200
1218
|
afferentPaths: singleAfferentPaths,
|
|
1201
|
-
default:
|
|
1202
|
-
default: globalDefaultHandler,
|
|
1219
|
+
default: defaultHandler,
|
|
1203
1220
|
output
|
|
1204
1221
|
} = s;
|
|
1205
1222
|
const modalityConfigs = multipleModalities ? multipleModalities : singleModality ? [
|
|
1206
1223
|
{
|
|
1207
1224
|
modality: singleModality,
|
|
1208
1225
|
afferentPaths: singleAfferentPaths,
|
|
1209
|
-
default:
|
|
1226
|
+
default: defaultHandler
|
|
1210
1227
|
}
|
|
1211
1228
|
] : [];
|
|
1212
1229
|
const response = (payload, axon2, ctx) => {
|
|
1213
1230
|
const stimulation = ctx.stimulation;
|
|
1214
1231
|
const stimOptions = stimulation?.options;
|
|
1215
1232
|
const runGlobalDefault = () => {
|
|
1216
|
-
if (!
|
|
1233
|
+
if (!defaultHandler) {
|
|
1217
1234
|
throw new Error(
|
|
1218
1235
|
`modalityDendrite: No handler found for modality and no default handler provided`
|
|
1219
1236
|
);
|
|
1220
1237
|
}
|
|
1221
|
-
const res =
|
|
1238
|
+
const res = defaultHandler(payload, axon2, ctx);
|
|
1222
1239
|
if (res && typeof res.then === "function") {
|
|
1223
1240
|
return res.then(
|
|
1224
1241
|
(result) => output(result, axon2, ctx)
|
|
@@ -1237,7 +1254,7 @@ var neuron = (axon) => {
|
|
|
1237
1254
|
}
|
|
1238
1255
|
const stimAfferentPath = stimOptions.afferentPath;
|
|
1239
1256
|
const handler = stimAfferentPath && matchingConfig.afferentPaths ? matchingConfig.afferentPaths.get(stimAfferentPath) : void 0;
|
|
1240
|
-
const effectiveHandler = handler ?? matchingConfig.default ??
|
|
1257
|
+
const effectiveHandler = handler ?? matchingConfig.default ?? defaultHandler;
|
|
1241
1258
|
if (!effectiveHandler) {
|
|
1242
1259
|
throw new Error(
|
|
1243
1260
|
`modalityDendrite: No handler found for afferent path in modality and no default handler provided`
|
|
@@ -1272,11 +1289,16 @@ var neuron = (axon) => {
|
|
|
1272
1289
|
};
|
|
1273
1290
|
return api;
|
|
1274
1291
|
};
|
|
1275
|
-
var
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
|
|
1292
|
+
var neuronFactory = () => {
|
|
1293
|
+
const factory = {
|
|
1294
|
+
withCtx: () => factory,
|
|
1295
|
+
withGlobal: () => factory,
|
|
1296
|
+
neuron: (axon) => neuron(axon)
|
|
1297
|
+
};
|
|
1298
|
+
return factory;
|
|
1299
|
+
};
|
|
1300
|
+
var withCtx = () => neuronFactory();
|
|
1301
|
+
var withGlobal = () => neuronFactory();
|
|
1280
1302
|
var afferentPath = (parentAfferentPath) => {
|
|
1281
1303
|
return {
|
|
1282
1304
|
...parentAfferentPath !== void 0 && {
|
|
@@ -1305,6 +1327,14 @@ var createPersistRegistry = (namedNeurons) => {
|
|
|
1305
1327
|
}
|
|
1306
1328
|
return registry;
|
|
1307
1329
|
};
|
|
1330
|
+
var createCNS = (namedNeurons, options) => {
|
|
1331
|
+
const neurons = Object.values(namedNeurons).map(
|
|
1332
|
+
(entry) => entry && typeof entry === "object" && "neuron" in entry ? entry.neuron : entry
|
|
1333
|
+
);
|
|
1334
|
+
const cns = new CNS(neurons, options, options?.global);
|
|
1335
|
+
const registry = createPersistRegistry(namedNeurons);
|
|
1336
|
+
return { cns, registry };
|
|
1337
|
+
};
|
|
1308
1338
|
|
|
1309
1339
|
exports.CNS = CNS;
|
|
1310
1340
|
exports.CNSCollateral = CNSCollateral;
|
|
@@ -1313,9 +1343,12 @@ exports.CNSStimulationContextStore = CNSStimulationContextStore;
|
|
|
1313
1343
|
exports.CNSStimulationGate = CNSStimulationGate;
|
|
1314
1344
|
exports.afferentPath = afferentPath;
|
|
1315
1345
|
exports.collateral = collateral;
|
|
1346
|
+
exports.createCNS = createCNS;
|
|
1316
1347
|
exports.createPersistRegistry = createPersistRegistry;
|
|
1317
1348
|
exports.modality = modality;
|
|
1318
1349
|
exports.neuron = neuron;
|
|
1350
|
+
exports.neuronFactory = neuronFactory;
|
|
1319
1351
|
exports.withCtx = withCtx;
|
|
1352
|
+
exports.withGlobal = withGlobal;
|
|
1320
1353
|
//# sourceMappingURL=index.cjs.map
|
|
1321
1354
|
//# sourceMappingURL=index.cjs.map
|