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