@cnstra/core 2.4.0 → 2.5.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/CNS.d.ts CHANGED
@@ -6,6 +6,7 @@ import { CNSStimulation } from './CNSStimulation';
6
6
  import { TCNSSignal } from './types/TCNSSignal';
7
7
  import { CNSInstanceNeuronQueue } from './CNSInstanceNeuronQueue';
8
8
  import { TCNSStimulationResponse } from './types/TCNSStimulationResponse';
9
+ import { TCNSStimulationDrain } from './types/TCNSStimulationDrain';
9
10
  import { TCNSNeuronActivationTask } from './types/TCNSNeuronActivationTask';
10
11
  import { CNSNetwork } from './CNSNetwork';
11
12
  import { ICNS } from './interfaces/ICNS';
@@ -26,6 +27,10 @@ export declare class CNS<TNeuron extends TCNSNeuron<any, any>, TDendrite extends
26
27
  * Global response listeners applied to every stimulation.
27
28
  */
28
29
  private readonly globalResponseListeners;
30
+ /**
31
+ * Global batch-boundary listeners applied to every stimulation.
32
+ */
33
+ private readonly globalDrainListeners;
29
34
  /**
30
35
  * Shared, app/organism-wide value handed to every dendrite as `ctx.global`.
31
36
  * Stored untyped (the type is baked into neurons via `neuronFactory().withGlobal<T>()`);
@@ -34,6 +39,23 @@ export declare class CNS<TNeuron extends TCNSNeuron<any, any>, TDendrite extends
34
39
  readonly global?: unknown;
35
40
  constructor(neurons: TNeuron[], options?: TCNSOptions | undefined, global?: unknown);
36
41
  addResponseListener(listener: (response: TCNSStimulationResponse) => void | Promise<void>): () => void;
42
+ /**
43
+ * Register a listener called once at the end of every synchronous turn of
44
+ * every stimulation - see {@link TCNSStimulationDrain}. This is how an
45
+ * integration (a state manager, an in-memory database) installs a single
46
+ * commit point for the whole organism instead of passing `onDrain` per call.
47
+ *
48
+ * Returns an unsubscribe function.
49
+ */
50
+ addDrainListener(listener: (drain: TCNSStimulationDrain) => void): () => void;
51
+ /**
52
+ * Fan a drain out to the local and global listeners, or return undefined when
53
+ * there are none so the stimulation can skip the boundary check entirely.
54
+ *
55
+ * Listener errors are isolated: a drain is a notification, and one broken
56
+ * flush must not stop the others or derail the stimulation that triggered it.
57
+ */
58
+ private wrapOnDrain;
37
59
  private wrapOnResponse;
38
60
  stimulate(signalOrSignals: TCNSSignal<CNSCollateral<unknown>> | TCNSSignal<CNSCollateral<unknown>>[], options?: TCNSStimulationOptions<TCNSStimulationResponse>): CNSStimulation<TNeuron, TDendrite>;
39
61
  /**
package/dist/CNS.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"CNS.d.ts","sourceRoot":"","sources":["../src/CNS.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,qBAAa,GAAG,CACZ,OAAO,SAAS,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,EACpC,SAAS,SAAS,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAC7E,YAAW,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC;IA6B7B,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE;aACrB,OAAO,CAAC,EAAE,WAAW;IA5BzC;;OAEG;IACH,SAAgB,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAExD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,mBAAmB,kCACI;IAE1C;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAEjC;IAEP;;;;OAIG;IACH,SAAgB,MAAM,CAAC,EAAE,OAAO,CAAC;gBAGV,OAAO,EAAE,OAAO,EAAE,EACrB,OAAO,CAAC,EAAE,WAAW,YAAA,EACrC,MAAM,CAAC,EAAE,OAAO;IAMb,mBAAmB,CACtB,QAAQ,EAAE,CAAC,QAAQ,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACtE,MAAM,IAAI;IAWb,OAAO,CAAC,cAAc;IAmDf,SAAS,CACZ,eAAe,EACT,UAAU,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,GAClC,UAAU,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,EAAE,EAC1C,OAAO,CAAC,EAAE,sBAAsB,CAAC,uBAAuB,CAAC,GAC1D,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC;IAYrC;;OAEG;IACI,QAAQ,CACX,KAAK,EAAE,wBAAwB,CAAC,OAAO,CAAC,EAAE,EAC1C,OAAO,CAAC,EAAE,sBAAsB,CAAC,uBAAuB,CAAC,GAC1D,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC;CAWxC"}
1
+ {"version":3,"file":"CNS.d.ts","sourceRoot":"","sources":["../src/CNS.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,uBAAuB,EAAE,MAAM,iCAAiC,CAAC;AAC1E,OAAO,EAAE,oBAAoB,EAAE,MAAM,8BAA8B,CAAC;AACpE,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,IAAI,EAAE,MAAM,mBAAmB,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,qBAAa,GAAG,CACZ,OAAO,SAAS,UAAU,CAAC,GAAG,EAAE,GAAG,CAAC,EACpC,SAAS,SAAS,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,GAAG,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,CAC7E,YAAW,IAAI,CAAC,OAAO,EAAE,SAAS,CAAC;IAoC7B,SAAS,CAAC,QAAQ,CAAC,OAAO,EAAE,OAAO,EAAE;aACrB,OAAO,CAAC,EAAE,WAAW;IAnCzC;;OAEG;IACH,SAAgB,OAAO,EAAE,UAAU,CAAC,OAAO,EAAE,SAAS,CAAC,CAAC;IAExD;;;OAGG;IACH,SAAS,CAAC,QAAQ,CAAC,mBAAmB,kCACI;IAE1C;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,uBAAuB,CAEjC;IAEP;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAE9B;IAEP;;;;OAIG;IACH,SAAgB,MAAM,CAAC,EAAE,OAAO,CAAC;gBAGV,OAAO,EAAE,OAAO,EAAE,EACrB,OAAO,CAAC,EAAE,WAAW,YAAA,EACrC,MAAM,CAAC,EAAE,OAAO;IAMb,mBAAmB,CACtB,QAAQ,EAAE,CAAC,QAAQ,EAAE,uBAAuB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,GACtE,MAAM,IAAI;IAWb;;;;;;;OAOG;IACI,gBAAgB,CACnB,QAAQ,EAAE,CAAC,KAAK,EAAE,oBAAoB,KAAK,IAAI,GAChD,MAAM,IAAI;IAWb;;;;;;OAMG;IACH,OAAO,CAAC,WAAW;IAsBnB,OAAO,CAAC,cAAc;IAmDf,SAAS,CACZ,eAAe,EACT,UAAU,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,GAClC,UAAU,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC,EAAE,EAC1C,OAAO,CAAC,EAAE,sBAAsB,CAAC,uBAAuB,CAAC,GAC1D,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC;IAarC;;OAEG;IACI,QAAQ,CACX,KAAK,EAAE,wBAAwB,CAAC,OAAO,CAAC,EAAE,EAC1C,OAAO,CAAC,EAAE,sBAAsB,CAAC,uBAAuB,CAAC,GAC1D,cAAc,CAAC,OAAO,EAAE,SAAS,CAAC;CAYxC"}
@@ -9,6 +9,7 @@ import { TCNSNeuronActivationTask } from './types/TCNSNeuronActivationTask';
9
9
  import { CNS } from './CNS';
10
10
  import { TCNSNeuronActivationTaskFailure } from './types/TCNSNeuronActivationTaskFailure';
11
11
  import { TCNSStimulationResponse } from './types/TCNSStimulationResponse';
12
+ import { TCNSStimulationDrain } from './types/TCNSStimulationDrain';
12
13
  import { CNSCollateral } from './CNSCollateral';
13
14
  export declare class CNSStimulation<TNeuron extends TCNSNeuron<any, any>, TDendrite extends TCNSDendrite<any, any, any> = TCNSDendrite<any, any, any>> {
14
15
  readonly cns: CNS<TNeuron, TDendrite>;
@@ -23,9 +24,11 @@ export declare class CNSStimulation<TNeuron extends TCNSNeuron<any, any>, TDendr
23
24
  private activeTasks?;
24
25
  private pumping;
25
26
  private needsPump;
27
+ private turnDepth;
26
28
  private readonly neuronVisitMap?;
27
29
  private readonly instanceNeuronQueue;
28
30
  private scheduledCount;
31
+ private reservedActivations;
29
32
  private pendingTasks?;
30
33
  private failedTasks?;
31
34
  private completed?;
@@ -40,12 +43,18 @@ export declare class CNSStimulation<TNeuron extends TCNSNeuron<any, any>, TDendr
40
43
  * undefined when there is neither a local nor any global listener.
41
44
  */
42
45
  private readonly onResponse?;
46
+ /**
47
+ * The (already wrapped) drain listener, or undefined when there is neither a
48
+ * local nor any global one. When undefined, endTurn() bails immediately, so a
49
+ * stimulation with no drain listener pays only the turn depth counter.
50
+ */
51
+ private readonly onDrain?;
43
52
  /**
44
53
  * Track active SCCs: SCC index -> count of active neurons in that SCC.
45
54
  * Only allocated when context auto-cleanup is enabled.
46
55
  */
47
56
  private readonly activeSccCounts?;
48
- constructor(cns: CNS<TNeuron, TDendrite>, instanceNeuronQueue: CNSInstanceNeuronQueue<TNeuron>, options?: TCNSStimulationOptions<TCNSStimulationResponse> | undefined, onResponse?: (response: TCNSStimulationResponse) => void | Promise<void>);
57
+ constructor(cns: CNS<TNeuron, TDendrite>, instanceNeuronQueue: CNSInstanceNeuronQueue<TNeuron>, options?: TCNSStimulationOptions<TCNSStimulationResponse> | undefined, onResponse?: (response: TCNSStimulationResponse) => void | Promise<void>, onDrain?: (drain: TCNSStimulationDrain) => void);
49
58
  private pushFailedTask;
50
59
  private finalizeResolve;
51
60
  private finalizeReject;
@@ -59,6 +68,29 @@ export declare class CNSStimulation<TNeuron extends TCNSNeuron<any, any>, TDendr
59
68
  private qEnqueue;
60
69
  private qGetQueuedTasks;
61
70
  private pump;
71
+ /**
72
+ * Open a synchronous turn. Turns nest: a drain is announced only when the
73
+ * outermost one closes, so an enqueue that happens while the pump is already
74
+ * running does not produce its own boundary.
75
+ */
76
+ private beginTurn;
77
+ /**
78
+ * Close a synchronous turn and, if it was the outermost and nothing more can
79
+ * run without yielding to the event loop, announce the drain.
80
+ *
81
+ * This is the only place the synchronous frontier is observable. A response
82
+ * cannot serve the purpose: responses are emitted when an activation
83
+ * *finishes*, but a turn just as often ends when one *starts* and returns a
84
+ * promise - at which point there is nothing left to emit a response from.
85
+ */
86
+ private endTurn;
87
+ /**
88
+ * Activations that exist (or are reserved) but whose dendrite body has not
89
+ * been invoked yet: sitting in the ring buffer, created and held in
90
+ * pendingTasks, or reserved by an array fan-out mid-dispatch. Subscribers
91
+ * parked behind an async onResponse land here too - their body has not run.
92
+ */
93
+ private get pendingActivations();
62
94
  private tryResolveCompleted;
63
95
  waitUntilComplete(): Promise<void>;
64
96
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"CNSStimulation.d.ts","sourceRoot":"","sources":["../src/CNSStimulation.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"CNSStimulation.d.ts","sourceRoot":"","sources":["../src/CNSStimulation.ts"],"names":[],"mappings":"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"}
package/dist/index.cjs CHANGED
@@ -50,12 +50,14 @@ var CNSDendriteContext = class {
50
50
  }
51
51
  };
52
52
  var CNSStimulationResponseImpl = class {
53
- constructor(stimulation, store, inputSignal, outputSignal, queueLength, hops, error) {
53
+ constructor(stimulation, store, inputSignal, outputSignal, queueLength, pendingActivations, activeActivations, hops, error) {
54
54
  this.stimulation = stimulation;
55
55
  this.store = store;
56
56
  this.inputSignal = inputSignal;
57
57
  this.outputSignal = outputSignal;
58
58
  this.queueLength = queueLength;
59
+ this.pendingActivations = pendingActivations;
60
+ this.activeActivations = activeActivations;
59
61
  this.hops = hops;
60
62
  this.error = error;
61
63
  }
@@ -64,7 +66,7 @@ var CNSStimulationResponseImpl = class {
64
66
  }
65
67
  };
66
68
  var CNSStimulation = class {
67
- constructor(cns, instanceNeuronQueue, options, onResponse) {
69
+ constructor(cns, instanceNeuronQueue, options, onResponse, onDrain) {
68
70
  this.cns = cns;
69
71
  this.options = options;
70
72
  this.qHead = 0;
@@ -75,9 +77,20 @@ var CNSStimulation = class {
75
77
  // Re-entrancy guard for the pump loop.
76
78
  this.pumping = false;
77
79
  this.needsPump = false;
80
+ // Nesting depth of the current synchronous turn. Only the outermost close
81
+ // announces a drain - see beginTurn/endTurn.
82
+ this.turnDepth = 0;
78
83
  this.scheduledCount = 0;
84
+ // Activations belonging to signals of an in-progress array fan-out that have
85
+ // not been handed to processResponse yet, so the task objects do not exist.
86
+ // processResponse enqueues synchronously, so the first signal's whole subtree
87
+ // drains before the second signal is even looked at; without this counter
88
+ // that subtree's leaf sees an empty queue and reads as "all work done".
89
+ // Counted in activations, not signals, so every public counter shares a unit.
90
+ this.reservedActivations = 0;
79
91
  this.isCompleted = false;
80
92
  this.completionRejected = false;
93
+ this.onDrain = onDrain;
81
94
  this.ctxStore = options?.ctx;
82
95
  this.instanceNeuronQueue = instanceNeuronQueue;
83
96
  this.onResponse = onResponse;
@@ -174,18 +187,28 @@ var CNSStimulation = class {
174
187
  (cb) => {
175
188
  this.activeOperations--;
176
189
  this.activeTasks.delete(task);
177
- if (typeof cb === "function") cb();
178
- if (this.qSize > 0 && this.canStartOperation) {
179
- if (this.pumping) this.needsPump = true;
180
- else this.pump();
190
+ this.beginTurn();
191
+ try {
192
+ if (typeof cb === "function") cb();
193
+ if (this.qSize > 0 && this.canStartOperation) {
194
+ if (this.pumping) this.needsPump = true;
195
+ else this.pump();
196
+ }
197
+ } finally {
198
+ this.endTurn();
181
199
  }
182
200
  },
183
201
  (err) => {
184
202
  this.activeOperations--;
185
203
  this.activeTasks.delete(task);
186
- if (this.qSize > 0 && this.canStartOperation) {
187
- if (this.pumping) this.needsPump = true;
188
- else this.pump();
204
+ this.beginTurn();
205
+ try {
206
+ if (this.qSize > 0 && this.canStartOperation) {
207
+ if (this.pumping) this.needsPump = true;
208
+ else this.pump();
209
+ }
210
+ } finally {
211
+ this.endTurn();
189
212
  }
190
213
  throw err;
191
214
  }
@@ -201,10 +224,54 @@ var CNSStimulation = class {
201
224
  this.pump();
202
225
  }
203
226
  }
227
+ /**
228
+ * Open a synchronous turn. Turns nest: a drain is announced only when the
229
+ * outermost one closes, so an enqueue that happens while the pump is already
230
+ * running does not produce its own boundary.
231
+ */
232
+ beginTurn() {
233
+ this.turnDepth++;
234
+ }
235
+ /**
236
+ * Close a synchronous turn and, if it was the outermost and nothing more can
237
+ * run without yielding to the event loop, announce the drain.
238
+ *
239
+ * This is the only place the synchronous frontier is observable. A response
240
+ * cannot serve the purpose: responses are emitted when an activation
241
+ * *finishes*, but a turn just as often ends when one *starts* and returns a
242
+ * promise - at which point there is nothing left to emit a response from.
243
+ */
244
+ endTurn() {
245
+ if (--this.turnDepth > 0) return;
246
+ const onDrain = this.onDrain;
247
+ if (!onDrain) return;
248
+ if (this.pumping) return;
249
+ if (this.qSize > 0 && this.canStartOperation) return;
250
+ this.turnDepth++;
251
+ try {
252
+ onDrain({
253
+ stimulation: this,
254
+ queueLength: this.pendingActivations + this.activeOperations,
255
+ pendingActivations: this.pendingActivations,
256
+ activeActivations: this.activeOperations
257
+ });
258
+ } finally {
259
+ this.turnDepth--;
260
+ }
261
+ }
262
+ /**
263
+ * Activations that exist (or are reserved) but whose dendrite body has not
264
+ * been invoked yet: sitting in the ring buffer, created and held in
265
+ * pendingTasks, or reserved by an array fan-out mid-dispatch. Subscribers
266
+ * parked behind an async onResponse land here too - their body has not run.
267
+ */
268
+ get pendingActivations() {
269
+ return this.qSize + this.scheduledCount + this.reservedActivations;
270
+ }
204
271
  tryResolveCompleted() {
205
272
  if (this.isCompleted) return;
206
273
  const noActive = this.activeOperations === 0;
207
- const noPending = this.qSize + this.scheduledCount === 0;
274
+ const noPending = this.pendingActivations === 0;
208
275
  const aborted = !!this.options?.abortSignal?.aborted;
209
276
  if (noPending && noActive || aborted && noActive && !noPending) {
210
277
  if (aborted) {
@@ -276,10 +343,15 @@ var CNSStimulation = class {
276
343
  * Enqueue activation tasks directly into the stimulation queue
277
344
  */
278
345
  enqueueTasks(tasks) {
279
- for (const task of tasks) {
280
- this.qEnqueue(task);
346
+ this.beginTurn();
347
+ try {
348
+ for (const task of tasks) {
349
+ this.qEnqueue(task);
350
+ }
351
+ this.tryResolveCompleted();
352
+ } finally {
353
+ this.endTurn();
281
354
  }
282
- this.tryResolveCompleted();
283
355
  }
284
356
  get concurrencyEnabled() {
285
357
  return this.options?.concurrency !== void 0 && this.options?.concurrency > 0;
@@ -380,14 +452,19 @@ var CNSStimulation = class {
380
452
  }
381
453
  }
382
454
  if (!subscriber) {
455
+ const error = new Error(`Subscriber not found for activation task`);
383
456
  this.pushFailedTask({
384
457
  task: neuronActivationTask,
385
- error: new Error(
386
- `Subscriber not found for activation task`
387
- ),
458
+ error,
388
459
  aborted: false
389
460
  });
390
461
  return () => {
462
+ this.processResponseOrResponses(
463
+ void 0,
464
+ neuronActivationTask.input,
465
+ void 0,
466
+ error
467
+ );
391
468
  };
392
469
  }
393
470
  const neuron2 = subscriber.neuron;
@@ -434,13 +511,13 @@ var CNSStimulation = class {
434
511
  error: error instanceof Error ? error : new Error(String(error)),
435
512
  aborted: isAborted
436
513
  });
437
- this.processResponseOrResponses(
438
- neuron2,
439
- inputSignal,
440
- void 0,
441
- error
442
- );
443
514
  return () => {
515
+ this.processResponseOrResponses(
516
+ neuron2,
517
+ inputSignal,
518
+ void 0,
519
+ error
520
+ );
444
521
  };
445
522
  }
446
523
  const maxDuration = neuron2.maxDuration;
@@ -510,8 +587,26 @@ var CNSStimulation = class {
510
587
  this.processResponse(emitter, inputSignal, void 0, error);
511
588
  return;
512
589
  }
513
- for (const signal of outputSignalOrSignals) {
514
- this.processResponse(emitter, inputSignal, signal, error);
590
+ let perSignal;
591
+ if (!error) {
592
+ perSignal = new Array(outputSignalOrSignals.length);
593
+ let reserved = 0;
594
+ for (let i = 0; i < outputSignalOrSignals.length; i++) {
595
+ const c = outputSignalOrSignals[i]?.collateral;
596
+ const n = c ? this.cns.network.getSubscribers(c).length : 0;
597
+ perSignal[i] = n;
598
+ reserved += n;
599
+ }
600
+ this.reservedActivations += reserved;
601
+ }
602
+ for (let i = 0; i < outputSignalOrSignals.length; i++) {
603
+ if (perSignal) this.reservedActivations -= perSignal[i];
604
+ this.processResponse(
605
+ emitter,
606
+ inputSignal,
607
+ outputSignalOrSignals[i],
608
+ error
609
+ );
515
610
  }
516
611
  return;
517
612
  }
@@ -565,7 +660,9 @@ var CNSStimulation = class {
565
660
  this.ctxStore,
566
661
  inputSignal,
567
662
  outputSignal,
568
- this.qSize + this.scheduledCount,
663
+ this.pendingActivations + this.activeOperations,
664
+ this.pendingActivations,
665
+ this.activeOperations,
569
666
  this.options?.maxNeuronHops && emitter ? this.neuronVisitMap?.get(emitter) ?? 0 : void 0,
570
667
  error
571
668
  )
@@ -583,21 +680,35 @@ var CNSStimulation = class {
583
680
  if (maybePromise && typeof maybePromise.then === "function") {
584
681
  maybePromise.then(
585
682
  () => {
586
- for (let i = 0; i < subscriberActivationTasks.length; i++) {
587
- this.scheduledCount--;
588
- this.pendingTasks?.delete(subscriberActivationTasks[i]);
589
- this.qEnqueue(subscriberActivationTasks[i]);
683
+ this.beginTurn();
684
+ try {
685
+ for (let i = 0; i < subscriberActivationTasks.length; i++) {
686
+ this.scheduledCount--;
687
+ this.pendingTasks?.delete(
688
+ subscriberActivationTasks[i]
689
+ );
690
+ this.qEnqueue(subscriberActivationTasks[i]);
691
+ }
692
+ this.tryResolveCompleted();
693
+ } finally {
694
+ this.endTurn();
590
695
  }
591
- this.tryResolveCompleted();
592
696
  },
593
697
  (error2) => {
594
698
  this.onResponseError = error2 instanceof Error ? error2 : new Error(String(error2));
595
- for (let i = 0; i < subscriberActivationTasks.length; i++) {
596
- this.scheduledCount--;
597
- this.pendingTasks?.delete(subscriberActivationTasks[i]);
598
- this.qEnqueue(subscriberActivationTasks[i]);
699
+ this.beginTurn();
700
+ try {
701
+ for (let i = 0; i < subscriberActivationTasks.length; i++) {
702
+ this.scheduledCount--;
703
+ this.pendingTasks?.delete(
704
+ subscriberActivationTasks[i]
705
+ );
706
+ this.qEnqueue(subscriberActivationTasks[i]);
707
+ }
708
+ this.tryResolveCompleted();
709
+ } finally {
710
+ this.endTurn();
599
711
  }
600
- this.tryResolveCompleted();
601
712
  }
602
713
  );
603
714
  return;
@@ -610,12 +721,21 @@ var CNSStimulation = class {
610
721
  this.tryResolveCompleted();
611
722
  }
612
723
  responseToSignal(signalOrSignals) {
613
- if (Array.isArray(signalOrSignals)) {
614
- this.processResponseOrResponses(void 0, void 0, signalOrSignals);
615
- return;
724
+ this.beginTurn();
725
+ try {
726
+ if (Array.isArray(signalOrSignals)) {
727
+ this.processResponseOrResponses(
728
+ void 0,
729
+ void 0,
730
+ signalOrSignals
731
+ );
732
+ return;
733
+ }
734
+ this.processResponse(void 0, void 0, signalOrSignals);
735
+ this.tryResolveCompleted();
736
+ } finally {
737
+ this.endTurn();
616
738
  }
617
- this.processResponse(void 0, void 0, signalOrSignals);
618
- this.tryResolveCompleted();
619
739
  }
620
740
  };
621
741
 
@@ -962,6 +1082,10 @@ var CNS = class {
962
1082
  * Global response listeners applied to every stimulation.
963
1083
  */
964
1084
  this.globalResponseListeners = [];
1085
+ /**
1086
+ * Global batch-boundary listeners applied to every stimulation.
1087
+ */
1088
+ this.globalDrainListeners = [];
965
1089
  this.network = new CNSNetwork(this.neurons);
966
1090
  this.global = global;
967
1091
  }
@@ -975,6 +1099,50 @@ var CNS = class {
975
1099
  if (idx >= 0) this.globalResponseListeners.splice(idx, 1);
976
1100
  };
977
1101
  }
1102
+ /**
1103
+ * Register a listener called once at the end of every synchronous turn of
1104
+ * every stimulation - see {@link TCNSStimulationDrain}. This is how an
1105
+ * integration (a state manager, an in-memory database) installs a single
1106
+ * commit point for the whole organism instead of passing `onDrain` per call.
1107
+ *
1108
+ * Returns an unsubscribe function.
1109
+ */
1110
+ addDrainListener(listener) {
1111
+ this.globalDrainListeners.push(listener);
1112
+ let active = true;
1113
+ return () => {
1114
+ if (!active) return;
1115
+ active = false;
1116
+ const idx = this.globalDrainListeners.indexOf(listener);
1117
+ if (idx >= 0) this.globalDrainListeners.splice(idx, 1);
1118
+ };
1119
+ }
1120
+ /**
1121
+ * Fan a drain out to the local and global listeners, or return undefined when
1122
+ * there are none so the stimulation can skip the boundary check entirely.
1123
+ *
1124
+ * Listener errors are isolated: a drain is a notification, and one broken
1125
+ * flush must not stop the others or derail the stimulation that triggered it.
1126
+ */
1127
+ wrapOnDrain(local) {
1128
+ if (this.globalDrainListeners.length === 0 && !local) return void 0;
1129
+ return (d) => {
1130
+ if (local) {
1131
+ try {
1132
+ local(d);
1133
+ } catch (error) {
1134
+ console.error("[CNS] onDrain listener threw", error);
1135
+ }
1136
+ }
1137
+ for (let i = 0; i < this.globalDrainListeners.length; i++) {
1138
+ try {
1139
+ this.globalDrainListeners[i](d);
1140
+ } catch (error) {
1141
+ console.error("[CNS] drain listener threw", error);
1142
+ }
1143
+ }
1144
+ };
1145
+ }
978
1146
  wrapOnResponse(local) {
979
1147
  if (this.globalResponseListeners.length === 0 && !local) {
980
1148
  return void 0;
@@ -1022,7 +1190,8 @@ var CNS = class {
1022
1190
  this,
1023
1191
  this.instanceNeuronQueue,
1024
1192
  options,
1025
- wrapped
1193
+ wrapped,
1194
+ this.wrapOnDrain(options?.onDrain)
1026
1195
  );
1027
1196
  stimulation.responseToSignal(signalOrSignals);
1028
1197
  return stimulation;
@@ -1036,7 +1205,8 @@ var CNS = class {
1036
1205
  this,
1037
1206
  this.instanceNeuronQueue,
1038
1207
  options,
1039
- wrapped
1208
+ wrapped,
1209
+ this.wrapOnDrain(options?.onDrain)
1040
1210
  );
1041
1211
  stimulation.enqueueTasks(tasks);
1042
1212
  return stimulation;