@cnstra/core 1.7.0 → 1.8.1

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 (36) hide show
  1. package/README.md +16 -588
  2. package/dist/CNS.d.ts +12 -83
  3. package/dist/CNS.d.ts.map +1 -1
  4. package/dist/CNSNetwork.d.ts +87 -0
  5. package/dist/CNSNetwork.d.ts.map +1 -0
  6. package/dist/CNSNeuronActivationPump.d.ts +27 -0
  7. package/dist/CNSNeuronActivationPump.d.ts.map +1 -0
  8. package/dist/CNSStimulation.d.ts +27 -6
  9. package/dist/CNSStimulation.d.ts.map +1 -1
  10. package/dist/index.cjs +324 -304
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.ts +2 -1
  13. package/dist/index.d.ts.map +1 -1
  14. package/dist/index.js +324 -304
  15. package/dist/index.js.map +1 -1
  16. package/dist/interfaces/ICNS.d.ts +12 -24
  17. package/dist/interfaces/ICNS.d.ts.map +1 -1
  18. package/dist/interfaces/ICNSStimulationContextStore.d.ts +3 -2
  19. package/dist/interfaces/ICNSStimulationContextStore.d.ts.map +1 -1
  20. package/dist/types/TCNSDendrite.d.ts +2 -2
  21. package/dist/types/TCNSDendrite.d.ts.map +1 -1
  22. package/dist/types/TCNSNeuronActivationTask.d.ts +8 -0
  23. package/dist/types/TCNSNeuronActivationTask.d.ts.map +1 -0
  24. package/dist/types/TCNSNeuronActivationTaskFailure.d.ts +7 -0
  25. package/dist/types/TCNSNeuronActivationTaskFailure.d.ts.map +1 -0
  26. package/dist/types/TCNSStimulationOptions.d.ts +7 -4
  27. package/dist/types/TCNSStimulationOptions.d.ts.map +1 -1
  28. package/dist/types/TCNSStimulationResponse.d.ts +11 -4
  29. package/dist/types/TCNSStimulationResponse.d.ts.map +1 -1
  30. package/dist/types/TCNSStimulationSerializedContextValue.d.ts +4 -0
  31. package/dist/types/TCNSStimulationSerializedContextValue.d.ts.map +1 -0
  32. package/package.json +1 -1
  33. package/dist/CNSFunctionalQueue.d.ts +0 -26
  34. package/dist/CNSFunctionalQueue.d.ts.map +0 -1
  35. package/dist/types/TCNSSerializedSignal.d.ts +0 -7
  36. package/dist/types/TCNSSerializedSignal.d.ts.map +0 -1
package/dist/index.cjs CHANGED
@@ -24,8 +24,8 @@ var CNSStimulationContextStore = class {
24
24
  }
25
25
  };
26
26
 
27
- // src/CNSFunctionalQueue.ts
28
- var CNSFunctionalQueue = class {
27
+ // src/CNSNeuronActivationPump.ts
28
+ var CNSNeuronActivationPump = class {
29
29
  constructor(processor, concurrency, abortSignal, initialCapacity) {
30
30
  this.processor = processor;
31
31
  this.concurrency = concurrency;
@@ -36,6 +36,7 @@ var CNSFunctionalQueue = class {
36
36
  this.size = 0;
37
37
  this.capacity = 16;
38
38
  this.activeOperations = 0;
39
+ this.activeTasks = /* @__PURE__ */ new Set();
39
40
  // NEW: prevent re-entrant pump; defer extra runs
40
41
  this.pumping = false;
41
42
  this.needsPump = false;
@@ -63,17 +64,17 @@ var CNSFunctionalQueue = class {
63
64
  }
64
65
  dequeue() {
65
66
  if (this.size === 0) return void 0;
66
- const item = this.items[this.head];
67
+ const neuronActivationTask = this.items[this.head];
67
68
  this.items[this.head] = void 0;
68
69
  this.head = (this.head + 1) % this.capacity;
69
70
  this.size--;
70
- return item;
71
+ return neuronActivationTask;
71
72
  }
72
- enqueueItem(item) {
73
+ enqueueItem(neuronActivationTask) {
73
74
  if (this.size === this.capacity) {
74
75
  this.resize();
75
76
  }
76
- this.items[this.tail] = item;
77
+ this.items[this.tail] = neuronActivationTask;
77
78
  this.tail = (this.tail + 1) % this.capacity;
78
79
  this.size++;
79
80
  }
@@ -84,13 +85,15 @@ var CNSFunctionalQueue = class {
84
85
  }
85
86
  this.pumping = true;
86
87
  while (this.canStartOperation && this.size > 0) {
87
- const item = this.dequeue();
88
+ const neuronActivationTask = this.dequeue();
88
89
  this.activeOperations++;
89
- const ret = this.processor(item);
90
+ this.activeTasks.add(neuronActivationTask);
91
+ const ret = this.processor(neuronActivationTask);
90
92
  if (ret && typeof ret.then === "function") {
91
93
  ret.then(
92
94
  (cb) => {
93
95
  this.activeOperations--;
96
+ this.activeTasks.delete(neuronActivationTask);
94
97
  if (typeof cb === "function") cb();
95
98
  if (this.size > 0 && this.canStartOperation) {
96
99
  if (this.pumping) this.needsPump = true;
@@ -99,6 +102,7 @@ var CNSFunctionalQueue = class {
99
102
  },
100
103
  (err) => {
101
104
  this.activeOperations--;
105
+ this.activeTasks.delete(neuronActivationTask);
102
106
  if (this.size > 0 && this.canStartOperation) {
103
107
  if (this.pumping) this.needsPump = true;
104
108
  else this.pump();
@@ -109,6 +113,7 @@ var CNSFunctionalQueue = class {
109
113
  continue;
110
114
  }
111
115
  this.activeOperations--;
116
+ this.activeTasks.delete(neuronActivationTask);
112
117
  if (typeof ret === "function") ret();
113
118
  }
114
119
  this.pumping = false;
@@ -117,8 +122,8 @@ var CNSFunctionalQueue = class {
117
122
  this.pump();
118
123
  }
119
124
  }
120
- enqueue(x) {
121
- this.enqueueItem(x);
125
+ enqueue(neuronActivationTask) {
126
+ this.enqueueItem(neuronActivationTask);
122
127
  if (!this.pumping) this.pump();
123
128
  else this.needsPump = true;
124
129
  }
@@ -128,6 +133,18 @@ var CNSFunctionalQueue = class {
128
133
  getActiveOperationsCount() {
129
134
  return this.activeOperations;
130
135
  }
136
+ getQueuedTasks() {
137
+ const result = [];
138
+ let index = this.head;
139
+ for (let i = 0; i < this.size; i++) {
140
+ result.push(this.items[index]);
141
+ index = (index + 1) % this.capacity;
142
+ }
143
+ return result;
144
+ }
145
+ getActiveTasks() {
146
+ return Array.from(this.activeTasks);
147
+ }
131
148
  };
132
149
 
133
150
  // src/CNSStimulation.ts
@@ -136,6 +153,8 @@ var CNSStimulation = class {
136
153
  this.cns = cns;
137
154
  this.options = options;
138
155
  this.scheduledCount = 0;
156
+ this.pendingTasks = /* @__PURE__ */ new Set();
157
+ this.failedTasks = [];
139
158
  this.completed = new Promise((resolve, reject) => {
140
159
  this.resolveCompleted = resolve;
141
160
  this.rejectCompleted = reject;
@@ -145,10 +164,12 @@ var CNSStimulation = class {
145
164
  * Track active SCCs: SCC index -> count of active neurons in that SCC
146
165
  */
147
166
  this.activeSccCounts = /* @__PURE__ */ new Map();
148
- this.ctx = options?.ctx ?? options?.createContextStore?.() ?? new CNSStimulationContextStore();
167
+ this.ctx = options?.ctx ?? new CNSStimulationContextStore(
168
+ new Map(Object.entries(options?.contextValues ?? {}))
169
+ );
149
170
  this.id = options?.stimulationId || Math.random().toString(36).slice(2);
150
- this.queue = new CNSFunctionalQueue(
151
- (item) => this.processQueueItem(item),
171
+ this.queue = new CNSNeuronActivationPump(
172
+ (neuronActivationTask) => this.executeActivationTask(neuronActivationTask),
152
173
  options?.concurrency,
153
174
  options?.abortSignal
154
175
  );
@@ -156,25 +177,81 @@ var CNSStimulation = class {
156
177
  if (this.options?.maxNeuronHops) {
157
178
  this.nueronVisitMap = /* @__PURE__ */ new Map();
158
179
  }
159
- if (this.options?.abortSignal) {
160
- this.options.abortSignal.addEventListener("abort", () => {
161
- this.tryResolveCompleted();
162
- });
163
- }
164
180
  }
165
181
  tryResolveCompleted() {
166
182
  if (this.isCompleted) return;
167
183
  const noActive = this.queue.getActiveOperationsCount() === 0;
168
184
  const noPending = this.queue.length + this.scheduledCount === 0;
169
185
  const aborted = !!this.options?.abortSignal?.aborted;
170
- if (noPending && noActive || aborted && noActive) {
186
+ if (noPending && noActive || aborted && noActive && !noPending) {
187
+ if (aborted) {
188
+ const queuedTasks = this.queue.getQueuedTasks();
189
+ for (const task of [...queuedTasks]) {
190
+ const alreadyTracked = this.failedTasks.some(
191
+ (ft) => ft.task.neuronId === task.neuronId && ft.task.dendriteCollateralName === task.dendriteCollateralName && ft.task.stimulationId === task.stimulationId
192
+ );
193
+ if (!alreadyTracked) {
194
+ this.failedTasks.push({
195
+ task,
196
+ error: new Error(
197
+ "Task aborted - not started due to abort signal"
198
+ ),
199
+ aborted: true
200
+ });
201
+ }
202
+ }
203
+ this.isCompleted = true;
204
+ this.rejectCompleted(new Error("Stimulation aborted"));
205
+ return;
206
+ }
171
207
  this.isCompleted = true;
172
- this.resolveCompleted();
208
+ if (this.failedTasks.length > 0 || this.onResponseError) {
209
+ const error = this.onResponseError || new Error(
210
+ `Stimulation completed with ${this.failedTasks.length} failed task(s)`
211
+ );
212
+ this.rejectCompleted(error);
213
+ } else {
214
+ this.resolveCompleted();
215
+ }
173
216
  }
174
217
  }
175
218
  waitUntilComplete() {
176
219
  return this.completed;
177
220
  }
221
+ /**
222
+ * Returns all current activation tasks: queued, active, and pending (scheduled but not yet enqueued)
223
+ */
224
+ getAllActivationTasks() {
225
+ const queuedTasks = this.queue.getQueuedTasks();
226
+ const activeTasks = this.queue.getActiveTasks();
227
+ const pendingTasks = Array.from(this.pendingTasks);
228
+ return [...queuedTasks, ...activeTasks, ...pendingTasks];
229
+ }
230
+ /**
231
+ * Returns all tasks that failed or were aborted
232
+ */
233
+ getFailedTasks() {
234
+ return [...this.failedTasks];
235
+ }
236
+ /**
237
+ * Get the context store for this stimulation
238
+ */
239
+ getContext() {
240
+ return this.ctx;
241
+ }
242
+ /**
243
+ * Enqueue activation tasks directly into the stimulation queue
244
+ */
245
+ enqueueTasks(tasks) {
246
+ for (const task of tasks) {
247
+ const taskWithId = {
248
+ ...task,
249
+ stimulationId: this.id
250
+ };
251
+ this.queue.enqueue(taskWithId);
252
+ }
253
+ this.tryResolveCompleted();
254
+ }
178
255
  get concurrencyEnabled() {
179
256
  return this.options?.concurrency !== void 0 && this.options?.concurrency > 0;
180
257
  }
@@ -199,7 +276,7 @@ var CNSStimulation = class {
199
276
  * Increment the active count for the SCC containing this neuron
200
277
  */
201
278
  incrementSccCount(neuronId) {
202
- const sccIndex = this.cns.getSccIndexByNeuronName(neuronId);
279
+ const sccIndex = this.cns.network.getSccIndexByNeuronName(neuronId);
203
280
  if (sccIndex === void 0) return;
204
281
  const currentCount = this.activeSccCounts.get(sccIndex) || 0;
205
282
  this.activeSccCounts.set(sccIndex, currentCount + 1);
@@ -208,7 +285,7 @@ var CNSStimulation = class {
208
285
  * Decrement the active count for the SCC containing this neuron
209
286
  */
210
287
  decrementSccCount(neuronId) {
211
- const sccIndex = this.cns.getSccIndexByNeuronName(neuronId);
288
+ const sccIndex = this.cns.network.getSccIndexByNeuronName(neuronId);
212
289
  if (sccIndex === void 0) return;
213
290
  const currentCount = this.activeSccCounts.get(sccIndex) || 0;
214
291
  const nextCount = Math.max(0, currentCount - 1);
@@ -224,7 +301,7 @@ var CNSStimulation = class {
224
301
  */
225
302
  canNeuronBeGuaranteedDone(neuronId) {
226
303
  if (!this.autoCleanupContextsEnabled) return false;
227
- return this.cns.canNeuronBeGuaranteedDone(
304
+ return this.cns.network.canNeuronBeGuaranteedDone(
228
305
  neuronId,
229
306
  this.activeSccCounts
230
307
  );
@@ -247,45 +324,70 @@ var CNSStimulation = class {
247
324
  );
248
325
  }
249
326
  }
250
- const serialized = {
327
+ const neuronActivationTask = {
328
+ stimulationId: this.id,
251
329
  neuronId: subscriber.neuron.name,
252
330
  dendriteCollateralName: subscriber.dendrite.collateral.name,
253
- input: inputSignal ? {
254
- collateralName: inputSignal.collateralName,
255
- payload: inputSignal.payload
256
- } : void 0
331
+ input: inputSignal
257
332
  };
258
- return serialized;
333
+ return neuronActivationTask;
259
334
  }
260
- processQueueItem(item) {
261
- const subscribers = this.cns.getSubscribers(
262
- item.dendriteCollateralName
335
+ executeActivationTask(neuronActivationTask) {
336
+ const subscribers = this.cns.network.getSubscribers(
337
+ neuronActivationTask.dendriteCollateralName
263
338
  );
264
339
  const subscriber = subscribers.find(
265
- (s) => s.neuron.name === item.neuronId
340
+ (s) => s.neuron.name === neuronActivationTask.neuronId
266
341
  );
267
- if (!subscriber) return () => {
268
- };
342
+ if (!subscriber) {
343
+ this.failedTasks.push({
344
+ task: neuronActivationTask,
345
+ error: new Error(
346
+ `Subscriber not found for neuron "${neuronActivationTask.neuronId}" and collateral "${neuronActivationTask.dendriteCollateralName}"`
347
+ ),
348
+ aborted: false
349
+ });
350
+ return () => {
351
+ };
352
+ }
269
353
  const neuron2 = subscriber.neuron;
270
354
  const dendrite = subscriber.dendrite;
271
- const inputSignal = item.input ? {
272
- collateralName: item.input.collateralName,
273
- payload: item.input.payload
355
+ const inputSignal = neuronActivationTask.input ? {
356
+ collateralName: neuronActivationTask.input.collateralName,
357
+ payload: neuronActivationTask.input.payload
274
358
  } : void 0;
275
359
  const starter = () => {
276
360
  this.markNeuronActive(neuron2.name);
277
- const response = dendrite.response(
278
- inputSignal?.payload,
279
- neuron2.axon,
280
- {
281
- get: () => this.ctx.get(neuron2.name),
282
- set: (value) => this.ctx.set(neuron2.name, value),
283
- delete: () => this.ctx.delete(neuron2.name),
284
- abortSignal: this.options?.abortSignal,
285
- cns: this.cns,
286
- stimulationId: this.id
287
- }
288
- );
361
+ let response;
362
+ try {
363
+ response = dendrite.response(
364
+ inputSignal?.payload,
365
+ neuron2.axon,
366
+ {
367
+ get: () => this.ctx.get(neuron2.name),
368
+ set: (value) => this.ctx.set(neuron2.name, value),
369
+ delete: () => this.ctx.delete(neuron2.name),
370
+ abortSignal: this.options?.abortSignal,
371
+ cns: this.cns,
372
+ stimulationId: this.id
373
+ }
374
+ );
375
+ } catch (error) {
376
+ this.markNeuronInactive(neuron2.name);
377
+ const isAborted = this.options?.abortSignal?.aborted ?? false;
378
+ this.failedTasks.push({
379
+ task: neuronActivationTask,
380
+ error: error instanceof Error ? error : new Error(String(error)),
381
+ aborted: isAborted
382
+ });
383
+ this.processResponseOrResponses(
384
+ inputSignal,
385
+ void 0,
386
+ error
387
+ );
388
+ return () => {
389
+ };
390
+ }
289
391
  const maxDuration = neuron2.maxDuration;
290
392
  const asPromise = response instanceof Promise ? response : Promise.resolve(response);
291
393
  const timedPromise = maxDuration && maxDuration > 0 ? new Promise((resolve, reject) => {
@@ -322,6 +424,12 @@ var CNSStimulation = class {
322
424
  (error) => {
323
425
  return () => {
324
426
  this.markNeuronInactive(neuron2.name);
427
+ const isAborted = this.options?.abortSignal?.aborted ?? false;
428
+ this.failedTasks.push({
429
+ task: neuronActivationTask,
430
+ error: error instanceof Error ? error : new Error(String(error)),
431
+ aborted: isAborted
432
+ });
325
433
  return this.processResponseOrResponses(
326
434
  inputSignal,
327
435
  void 0,
@@ -357,60 +465,78 @@ var CNSStimulation = class {
357
465
  }
358
466
  processResponse(inputSignal, outputSignal, error) {
359
467
  const collateralName = outputSignal?.collateralName;
360
- const ownerNeuron = collateralName ? this.cns.getParentNeuronByCollateralName(collateralName) : void 0;
361
- const subscribers = collateralName ? this.cns.getSubscribers(collateralName) : [];
362
- const subscriberQueueItems = [];
468
+ const ownerNeuron = collateralName ? this.cns.network.getParentNeuronByCollateralName(collateralName) : void 0;
469
+ const subscribers = collateralName ? this.cns.network.getSubscribers(collateralName) : [];
470
+ const subscriberActivationTasks = [];
363
471
  if (collateralName && !error) {
364
472
  if (ownerNeuron) {
365
473
  this.cleanupCtxIfNeeded(ownerNeuron);
366
474
  }
367
475
  for (let i = 0; i < subscribers.length; i++) {
368
- subscriberQueueItems.push(
369
- this.createSubscriberQueueItem(subscribers[i], outputSignal)
476
+ const task = this.createSubscriberQueueItem(
477
+ subscribers[i],
478
+ outputSignal
370
479
  );
480
+ subscriberActivationTasks.push(task);
481
+ this.pendingTasks.add(task);
371
482
  }
372
483
  }
373
- this.scheduledCount += subscriberQueueItems.length;
484
+ this.scheduledCount += subscriberActivationTasks.length;
374
485
  let maybePromise;
375
486
  try {
376
487
  maybePromise = this.options?.onResponse?.({
377
488
  inputSignal,
378
489
  outputSignal,
379
- ctx: this.ctx,
490
+ contextValue: this.ctx.getAll(),
380
491
  queueLength: this.queue.length + this.scheduledCount,
492
+ stimulation: this,
381
493
  stimulationId: this.id,
382
494
  hops: this.options?.maxNeuronHops && ownerNeuron ? this.nueronVisitMap?.get(ownerNeuron.name) ?? 0 : void 0,
383
495
  error
384
496
  });
385
497
  } catch (e) {
386
- this.rejectCompleted(e);
498
+ this.onResponseError = e instanceof Error ? e : new Error(String(e));
499
+ for (let i = 0; i < subscriberActivationTasks.length; i++) {
500
+ this.scheduledCount--;
501
+ this.pendingTasks.delete(subscriberActivationTasks[i]);
502
+ this.queue.enqueue(subscriberActivationTasks[i]);
503
+ }
504
+ this.tryResolveCompleted();
387
505
  return;
388
506
  }
389
507
  if (maybePromise && typeof maybePromise.then === "function") {
390
508
  maybePromise.then(
391
509
  () => {
392
- for (let i = 0; i < subscriberQueueItems.length; i++) {
510
+ for (let i = 0; i < subscriberActivationTasks.length; i++) {
393
511
  this.scheduledCount--;
394
- this.queue.enqueue(subscriberQueueItems[i]);
512
+ this.pendingTasks.delete(subscriberActivationTasks[i]);
513
+ this.queue.enqueue(subscriberActivationTasks[i]);
395
514
  }
396
515
  this.tryResolveCompleted();
397
516
  },
398
517
  (error2) => {
399
- this.rejectCompleted(error2);
518
+ this.onResponseError = error2 instanceof Error ? error2 : new Error(String(error2));
519
+ for (let i = 0; i < subscriberActivationTasks.length; i++) {
520
+ this.scheduledCount--;
521
+ this.pendingTasks.delete(subscriberActivationTasks[i]);
522
+ this.queue.enqueue(subscriberActivationTasks[i]);
523
+ }
524
+ this.tryResolveCompleted();
400
525
  }
401
526
  );
402
527
  return;
403
528
  }
404
- for (let i = 0; i < subscriberQueueItems.length; i++) {
529
+ for (let i = 0; i < subscriberActivationTasks.length; i++) {
405
530
  this.scheduledCount--;
406
- this.queue.enqueue(subscriberQueueItems[i]);
531
+ this.pendingTasks.delete(subscriberActivationTasks[i]);
532
+ this.queue.enqueue(subscriberActivationTasks[i]);
407
533
  }
408
534
  this.tryResolveCompleted();
409
535
  }
410
536
  responseToSignal(signalOrSignals) {
411
537
  if (Array.isArray(signalOrSignals)) {
412
538
  for (const signal of signalOrSignals) {
413
- const ownerNeuron2 = this.cns.getParentNeuronByCollateralName(
539
+ const ownerNeuron2 = this.cns.network.getParentNeuronByCollateralName(
414
540
  signal.collateralName
415
541
  );
416
542
  if (ownerNeuron2) {
@@ -420,7 +546,7 @@ var CNSStimulation = class {
420
546
  this.processResponseOrResponses(void 0, signalOrSignals);
421
547
  return;
422
548
  }
423
- const ownerNeuron = this.cns.getParentNeuronByCollateralName(
549
+ const ownerNeuron = this.cns.network.getParentNeuronByCollateralName(
424
550
  signalOrSignals.collateralName
425
551
  );
426
552
  if (ownerNeuron) {
@@ -431,88 +557,6 @@ var CNSStimulation = class {
431
557
  }
432
558
  };
433
559
 
434
- // src/CNSTaskQueue.ts
435
- var CNSTaskQueue = class {
436
- constructor(concurrency) {
437
- this.concurrency = concurrency;
438
- this.pumping = false;
439
- this.needsPump = false;
440
- this.activeOperations = 0;
441
- this.items = [];
442
- }
443
- get canStartOperation() {
444
- const limit = this.concurrency ?? Infinity;
445
- return this.activeOperations < limit;
446
- }
447
- enqueue(task) {
448
- return new Promise((resolve, reject) => {
449
- this.items.push({ task, resolve, reject });
450
- if (!this.pumping) this.pump();
451
- else this.needsPump = true;
452
- });
453
- }
454
- pump() {
455
- if (this.pumping) {
456
- this.needsPump = true;
457
- return;
458
- }
459
- this.pumping = true;
460
- while (this.canStartOperation && this.items.length > 0) {
461
- const { task, resolve, reject } = this.items.shift();
462
- this.activeOperations++;
463
- try {
464
- const ret = task();
465
- if (ret && typeof ret.then === "function") {
466
- ret.then(
467
- (cb) => {
468
- try {
469
- if (typeof cb === "function") cb();
470
- resolve();
471
- } finally {
472
- this.activeOperations--;
473
- if (this.items.length > 0 && this.canStartOperation) {
474
- if (this.pumping) this.needsPump = true;
475
- else this.pump();
476
- }
477
- }
478
- },
479
- (err) => {
480
- try {
481
- reject(err);
482
- } finally {
483
- this.activeOperations--;
484
- if (this.items.length > 0 && this.canStartOperation) {
485
- if (this.pumping) this.needsPump = true;
486
- else this.pump();
487
- }
488
- }
489
- }
490
- );
491
- break;
492
- } else {
493
- try {
494
- if (typeof ret === "function") ret();
495
- resolve();
496
- } finally {
497
- this.activeOperations--;
498
- }
499
- }
500
- } catch (e) {
501
- try {
502
- reject(e);
503
- } finally {
504
- this.activeOperations--;
505
- }
506
- }
507
- }
508
- this.pumping = false;
509
- if (this.needsPump) {
510
- this.needsPump = false;
511
- this.pump();
512
- }
513
- }
514
- };
515
-
516
560
  // src/CNSInstanceNeuronQueue.ts
517
561
  var CNSInstanceNeuronQueue = class {
518
562
  constructor() {
@@ -568,20 +612,10 @@ var CNSInstanceNeuronQueue = class {
568
612
  }
569
613
  };
570
614
 
571
- // src/CNS.ts
572
- var CNS = class {
573
- constructor(neurons, options) {
615
+ // src/CNSNetwork.ts
616
+ var CNSNetwork = class {
617
+ constructor(neurons) {
574
618
  this.neurons = neurons;
575
- this.options = options;
576
- /**
577
- * Fast lookup: collateralName -> list of (neuron, dendrite) subscribers.
578
- * Built once at construction time.
579
- */
580
- this.subIndex = /* @__PURE__ */ new Map();
581
- this.neuronIndex = /* @__PURE__ */ new Map();
582
- this.dendriteIndex = /* @__PURE__ */ new Map();
583
- this.collateralIndex = /* @__PURE__ */ new Map();
584
- this.parentNeuronByCollateralName = /* @__PURE__ */ new Map();
585
619
  /**
586
620
  * Strongly Connected Components of the neuron graph.
587
621
  * Each component is a set of neuron IDs that can reach each other.
@@ -600,24 +634,17 @@ var CNS = class {
600
634
  */
601
635
  this.sccAncestors = /* @__PURE__ */ new Map();
602
636
  /**
603
- * Global per-neuron concurrency gates shared across all stimulations.
604
- */
605
- this.neuronGates = /* @__PURE__ */ new Map();
606
- /**
607
- * Global task queue used by stimulation to schedule per-neuron gated work.
608
- * Does not replace per-stimulation concurrency; only coordinates global per-neuron limits.
609
- */
610
- this.globalTaskQueue = new CNSTaskQueue();
611
- this.instanceNeuronQueue = new CNSInstanceNeuronQueue();
612
- /**
613
- * Global response listeners applied to every stimulation.
637
+ * Fast lookup: collateralName -> list of (neuron, dendrite) subscribers.
638
+ * Built once at construction time.
614
639
  */
615
- this.globalResponseListeners = [];
640
+ this.subIndex = /* @__PURE__ */ new Map();
641
+ this.neuronIndex = /* @__PURE__ */ new Map();
642
+ this.dendriteIndex = /* @__PURE__ */ new Map();
643
+ this.collateralIndex = /* @__PURE__ */ new Map();
644
+ this.parentNeuronByCollateralName = /* @__PURE__ */ new Map();
616
645
  this.validateUniqueIdentifiers();
617
646
  this.buildIndexes();
618
- if (this.options?.autoCleanupContexts) {
619
- this.buildSCC();
620
- }
647
+ this.buildSCC();
621
648
  }
622
649
  /**
623
650
  * Validate uniqueness constraints:
@@ -647,126 +674,6 @@ var CNS = class {
647
674
  );
648
675
  }
649
676
  }
650
- addResponseListener(listener) {
651
- this.globalResponseListeners.push(listener);
652
- let active = true;
653
- return () => {
654
- if (!active) return;
655
- active = false;
656
- const idx = this.globalResponseListeners.indexOf(listener);
657
- if (idx >= 0) this.globalResponseListeners.splice(idx, 1);
658
- };
659
- }
660
- wrapOnResponse(local) {
661
- if (this.globalResponseListeners.length === 0 && !local) {
662
- return () => {
663
- };
664
- }
665
- return (r) => {
666
- let anyPromise = false;
667
- const promises = [];
668
- if (local) {
669
- const res = local(r);
670
- if (res && typeof res.then === "function") {
671
- anyPromise = true;
672
- promises.push(res);
673
- }
674
- }
675
- for (let i = 0; i < this.globalResponseListeners.length; i++) {
676
- const res = this.globalResponseListeners[i](r);
677
- if (res && typeof res.then === "function") {
678
- anyPromise = true;
679
- promises.push(res);
680
- }
681
- }
682
- if (anyPromise) {
683
- return Promise.allSettled(promises).then((results) => {
684
- const rejected = results.find(
685
- (r2) => r2.status === "rejected"
686
- );
687
- if (rejected) throw rejected.reason;
688
- });
689
- }
690
- };
691
- }
692
- runWithNeuronConcurrency(neuron2, fn) {
693
- const limit = neuron2.concurrency;
694
- if (limit === void 0 || limit <= 0) {
695
- return this.globalTaskQueue.enqueue(fn);
696
- }
697
- let gate = this.neuronGates.get(neuron2.name);
698
- if (!gate) {
699
- gate = { limit, active: 0, waiters: [] };
700
- this.neuronGates.set(neuron2.name, gate);
701
- } else {
702
- gate.limit = limit;
703
- }
704
- const wrap = (cb) => {
705
- return () => {
706
- try {
707
- cb();
708
- } finally {
709
- gate.active = Math.max(0, gate.active - 1);
710
- if (gate.waiters.length > 0) {
711
- const next = gate.waiters.shift();
712
- next();
713
- }
714
- }
715
- };
716
- };
717
- if (gate.active < gate.limit) {
718
- gate.active++;
719
- return this.globalTaskQueue.enqueue(() => {
720
- const ret = fn();
721
- if (ret && typeof ret.then === "function") {
722
- return ret.then((cb) => wrap(cb));
723
- }
724
- return wrap(ret);
725
- });
726
- }
727
- return new Promise((resolve) => {
728
- const starter = () => {
729
- gate.active++;
730
- this.globalTaskQueue.enqueue(() => {
731
- const ret = fn();
732
- if (ret && typeof ret.then === "function") {
733
- return ret.then(
734
- (cb) => wrap(cb)
735
- );
736
- }
737
- return wrap(ret);
738
- }).then(resolve);
739
- };
740
- gate.waiters.push(starter);
741
- });
742
- }
743
- buildIndexes() {
744
- this.subIndex.clear();
745
- this.parentNeuronByCollateralName.clear();
746
- for (const neuron2 of this.neurons) {
747
- this.neuronIndex.set(neuron2.name, neuron2);
748
- for (const dendrite of neuron2.dendrites) {
749
- const key = dendrite.collateral.name;
750
- const arr = this.subIndex.get(key) ?? [];
751
- arr.push({ neuron: neuron2, dendrite });
752
- this.subIndex.set(
753
- key,
754
- arr
755
- );
756
- this.dendriteIndex.set(key, dendrite);
757
- }
758
- Object.values(neuron2.axon).forEach((collateral2) => {
759
- this.parentNeuronByCollateralName.set(
760
- collateral2.name,
761
- neuron2
762
- );
763
- this.collateralIndex.set(
764
- collateral2.name,
765
- collateral2
766
- );
767
- });
768
- }
769
- }
770
677
  /**
771
678
  * Build the neuron graph where neurons are connected if one can stimulate the other.
772
679
  */
@@ -811,7 +718,7 @@ var CNS = class {
811
718
  stack.push(neuronId);
812
719
  onStack.add(neuronId);
813
720
  const neighbors = graph.get(neuronId) || /* @__PURE__ */ new Set();
814
- for (const neighbor of neighbors) {
721
+ for (const neighbor of Array.from(neighbors)) {
815
722
  if (!index.has(neighbor)) {
816
723
  strongConnect(neighbor);
817
724
  lowlink.set(
@@ -843,7 +750,7 @@ var CNS = class {
843
750
  }
844
751
  this.stronglyConnectedComponents = components;
845
752
  for (let i = 0; i < components.length; i++) {
846
- for (const neuronId of components[i]) {
753
+ for (const neuronId of Array.from(components[i])) {
847
754
  this.neuronToSCC.set(neuronId, i);
848
755
  }
849
756
  }
@@ -860,9 +767,9 @@ var CNS = class {
860
767
  }
861
768
  for (let i = 0; i < this.stronglyConnectedComponents.length; i++) {
862
769
  const scc = this.stronglyConnectedComponents[i];
863
- for (const neuronId of scc) {
770
+ for (const neuronId of Array.from(scc)) {
864
771
  const neighbors = neuronGraph.get(neuronId) || /* @__PURE__ */ new Set();
865
- for (const neighborId of neighbors) {
772
+ for (const neighborId of Array.from(neighbors)) {
866
773
  const neighborSccIndex = this.neuronToSCC.get(neighborId);
867
774
  if (neighborSccIndex !== void 0 && neighborSccIndex !== i) {
868
775
  this.sccDag.get(neighborSccIndex).add(i);
@@ -892,11 +799,11 @@ var CNS = class {
892
799
  while (queue.length > 0) {
893
800
  const current = queue.shift();
894
801
  const outgoingEdges = this.getOutgoingEdges(current);
895
- for (const neighbor of outgoingEdges) {
802
+ for (const neighbor of Array.from(outgoingEdges)) {
896
803
  const neighborAncestors = this.sccAncestors.get(neighbor);
897
804
  neighborAncestors.add(current);
898
805
  const currentAncestors = this.sccAncestors.get(current);
899
- for (const ancestor of currentAncestors) {
806
+ for (const ancestor of Array.from(currentAncestors)) {
900
807
  neighborAncestors.add(ancestor);
901
808
  }
902
809
  const newInDegree = (inDegree.get(neighbor) || 0) - 1;
@@ -912,7 +819,7 @@ var CNS = class {
912
819
  */
913
820
  getOutgoingEdges(sccIndex) {
914
821
  const outgoing = /* @__PURE__ */ new Set();
915
- for (const [targetScc, incomingEdges] of this.sccDag) {
822
+ for (const [targetScc, incomingEdges] of Array.from(this.sccDag)) {
916
823
  if (incomingEdges.has(sccIndex)) {
917
824
  outgoing.add(targetScc);
918
825
  }
@@ -947,13 +854,40 @@ var CNS = class {
947
854
  }
948
855
  const ancestors = this.sccAncestors.get(sccIndex);
949
856
  if (!ancestors) return true;
950
- for (const ancestorSccIndex of ancestors) {
857
+ for (const ancestorSccIndex of Array.from(ancestors)) {
951
858
  if (activeSccCounts.get(ancestorSccIndex) && activeSccCounts.get(ancestorSccIndex) > 0) {
952
859
  return false;
953
860
  }
954
861
  }
955
862
  return true;
956
863
  }
864
+ buildIndexes() {
865
+ this.subIndex.clear();
866
+ this.parentNeuronByCollateralName.clear();
867
+ for (const neuron2 of this.neurons) {
868
+ this.neuronIndex.set(neuron2.name, neuron2);
869
+ for (const dendrite of neuron2.dendrites) {
870
+ const key = dendrite.collateral.name;
871
+ const arr = this.subIndex.get(key) ?? [];
872
+ arr.push({ neuron: neuron2, dendrite });
873
+ this.subIndex.set(
874
+ key,
875
+ arr
876
+ );
877
+ this.dendriteIndex.set(key, dendrite);
878
+ }
879
+ Object.values(neuron2.axon).forEach((collateral2) => {
880
+ this.parentNeuronByCollateralName.set(
881
+ collateral2.name,
882
+ neuron2
883
+ );
884
+ this.collateralIndex.set(
885
+ collateral2.name,
886
+ collateral2
887
+ );
888
+ });
889
+ }
890
+ }
957
891
  getParentNeuronByCollateralName(collateralName) {
958
892
  return this.parentNeuronByCollateralName.get(collateralName);
959
893
  }
@@ -977,6 +911,80 @@ var CNS = class {
977
911
  getSubscribers(collateralName) {
978
912
  return this.subIndex.get(collateralName) ?? [];
979
913
  }
914
+ };
915
+
916
+ // src/CNS.ts
917
+ var CNS = class {
918
+ constructor(neurons, options) {
919
+ this.neurons = neurons;
920
+ this.options = options;
921
+ /**
922
+ * Global per-neuron concurrency gates shared across all stimulations.
923
+ */
924
+ this.neuronGates = /* @__PURE__ */ new Map();
925
+ /**
926
+ * Global task queue used by stimulation to schedule per-neuron gated work.
927
+ * Does not replace per-stimulation concurrency; only coordinates global per-neuron limits.
928
+ */
929
+ this.instanceNeuronQueue = new CNSInstanceNeuronQueue();
930
+ /**
931
+ * Global response listeners applied to every stimulation.
932
+ */
933
+ this.globalResponseListeners = [];
934
+ this.network = new CNSNetwork(this.neurons);
935
+ }
936
+ addResponseListener(listener) {
937
+ this.globalResponseListeners.push(listener);
938
+ let active = true;
939
+ return () => {
940
+ if (!active) return;
941
+ active = false;
942
+ const idx = this.globalResponseListeners.indexOf(listener);
943
+ if (idx >= 0) this.globalResponseListeners.splice(idx, 1);
944
+ };
945
+ }
946
+ wrapOnResponse(local) {
947
+ if (this.globalResponseListeners.length === 0 && !local) {
948
+ return () => {
949
+ };
950
+ }
951
+ return (r) => {
952
+ let anyPromise = false;
953
+ const promises = [];
954
+ if (local) {
955
+ try {
956
+ const res = local(r);
957
+ if (res && typeof res.then === "function") {
958
+ anyPromise = true;
959
+ promises.push(res);
960
+ }
961
+ } catch (error) {
962
+ anyPromise = true;
963
+ promises.push(Promise.reject(error));
964
+ }
965
+ }
966
+ for (let i = 0; i < this.globalResponseListeners.length; i++) {
967
+ try {
968
+ const res = this.globalResponseListeners[i](r);
969
+ if (res && typeof res.then === "function") {
970
+ anyPromise = true;
971
+ promises.push(res);
972
+ }
973
+ } catch (error) {
974
+ anyPromise = true;
975
+ promises.push(Promise.reject(error));
976
+ }
977
+ }
978
+ if (anyPromise) {
979
+ return Promise.allSettled(promises).then((results) => {
980
+ const rejected = results.find(
981
+ (r2) => r2.status === "rejected"
982
+ );
983
+ if (rejected) throw rejected.reason;
984
+ });
985
+ }
986
+ };
987
+ }
980
988
  stimulate(signalOrSignals, options) {
981
989
  const wrapped = this.wrapOnResponse(options?.onResponse);
982
990
  const stimulation = new CNSStimulation(this, this.instanceNeuronQueue, {
@@ -984,7 +992,19 @@ var CNS = class {
984
992
  onResponse: wrapped
985
993
  });
986
994
  stimulation.responseToSignal(signalOrSignals);
987
- return stimulation.waitUntilComplete();
995
+ return stimulation;
996
+ }
997
+ /**
998
+ * Start a stimulation with activation tasks directly
999
+ */
1000
+ activate(tasks, options) {
1001
+ const wrapped = this.wrapOnResponse(options?.onResponse);
1002
+ const stimulation = new CNSStimulation(this, this.instanceNeuronQueue, {
1003
+ ...options,
1004
+ onResponse: wrapped
1005
+ });
1006
+ stimulation.enqueueTasks(tasks);
1007
+ return stimulation;
988
1008
  }
989
1009
  };
990
1010