@cnstra/core 1.5.2 → 1.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -104,7 +104,7 @@ var CNSFunctionalQueue = class {
104
104
  throw err;
105
105
  }
106
106
  );
107
- break;
107
+ continue;
108
108
  }
109
109
  this.activeOperations--;
110
110
  if (typeof ret === "function") ret();
@@ -134,6 +134,11 @@ var CNSStimulation = class {
134
134
  this.cns = cns;
135
135
  this.options = options;
136
136
  this.scheduledCount = 0;
137
+ this.completed = new Promise((resolve, reject) => {
138
+ this.resolveCompleted = resolve;
139
+ this.rejectCompleted = reject;
140
+ });
141
+ this.isCompleted = false;
137
142
  /**
138
143
  * Track active SCCs: SCC index -> count of active neurons in that SCC
139
144
  */
@@ -149,6 +154,24 @@ var CNSStimulation = class {
149
154
  if (this.options?.maxNeuronHops) {
150
155
  this.nueronVisitMap = /* @__PURE__ */ new Map();
151
156
  }
157
+ if (this.options?.abortSignal) {
158
+ this.options.abortSignal.addEventListener("abort", () => {
159
+ this.tryResolveCompleted();
160
+ });
161
+ }
162
+ }
163
+ tryResolveCompleted() {
164
+ if (this.isCompleted) return;
165
+ const noActive = this.queue.getActiveOperationsCount() === 0;
166
+ const noPending = this.queue.length + this.scheduledCount === 0;
167
+ const aborted = !!this.options?.abortSignal?.aborted;
168
+ if (noPending && noActive || aborted && noActive) {
169
+ this.isCompleted = true;
170
+ this.resolveCompleted();
171
+ }
172
+ }
173
+ waitUntilComplete() {
174
+ return this.completed;
152
175
  }
153
176
  get concurrencyEnabled() {
154
177
  return this.options?.concurrency !== void 0 && this.options?.concurrency > 0;
@@ -222,15 +245,31 @@ var CNSStimulation = class {
222
245
  );
223
246
  }
224
247
  }
225
- return {
248
+ const serialized = {
226
249
  neuronId: subscriber.neuron.name,
227
- subscriber,
228
- inputSignal
250
+ dendriteCollateralName: subscriber.dendrite.collateral.name,
251
+ input: inputSignal ? {
252
+ collateralName: inputSignal.collateralName,
253
+ payload: inputSignal.payload
254
+ } : void 0
229
255
  };
256
+ return serialized;
230
257
  }
231
258
  processQueueItem(item) {
232
- const { subscriber, inputSignal } = item;
233
- const { neuron: neuron2, dendrite } = subscriber;
259
+ const subscribers = this.cns.getSubscribers(
260
+ item.dendriteCollateralName
261
+ );
262
+ const subscriber = subscribers.find(
263
+ (s) => s.neuron.name === item.neuronId
264
+ );
265
+ if (!subscriber) return () => {
266
+ };
267
+ const neuron2 = subscriber.neuron;
268
+ const dendrite = subscriber.dendrite;
269
+ const inputSignal = item.input ? {
270
+ collateralName: item.input.collateralName,
271
+ payload: item.input.payload
272
+ } : void 0;
234
273
  const starter = () => {
235
274
  this.markNeuronActive(neuron2.name);
236
275
  const response = dendrite.response(
@@ -241,15 +280,38 @@ var CNSStimulation = class {
241
280
  set: (value) => this.ctx.set(neuron2.name, value),
242
281
  delete: () => this.ctx.delete(neuron2.name),
243
282
  abortSignal: this.options?.abortSignal,
244
- cns: this.cns
283
+ cns: this.cns,
284
+ stimulationId: this.id
245
285
  }
246
286
  );
247
- if (response instanceof Promise) {
248
- return response.then(
287
+ const maxDuration = neuron2.maxDuration;
288
+ const asPromise = response instanceof Promise ? response : Promise.resolve(response);
289
+ const timedPromise = maxDuration && maxDuration > 0 ? new Promise((resolve, reject) => {
290
+ const t = setTimeout(() => {
291
+ const err = new Error(
292
+ `Neuron "${String(
293
+ neuron2.name
294
+ )}" exceeded maxDuration ${maxDuration}ms`
295
+ );
296
+ reject(err);
297
+ }, maxDuration);
298
+ asPromise.then(
299
+ (v) => {
300
+ clearTimeout(t);
301
+ resolve(v);
302
+ },
303
+ (e) => {
304
+ clearTimeout(t);
305
+ reject(e);
306
+ }
307
+ );
308
+ }) : asPromise;
309
+ if (response instanceof Promise || maxDuration) {
310
+ return timedPromise.then(
249
311
  (signal) => {
250
312
  return () => {
251
313
  this.markNeuronInactive(neuron2.name);
252
- return this.processResponse(
314
+ return this.processResponseOrResponses(
253
315
  inputSignal,
254
316
  signal
255
317
  );
@@ -258,7 +320,7 @@ var CNSStimulation = class {
258
320
  (error) => {
259
321
  return () => {
260
322
  this.markNeuronInactive(neuron2.name);
261
- return this.processResponse(
323
+ return this.processResponseOrResponses(
262
324
  inputSignal,
263
325
  void 0,
264
326
  error
@@ -269,7 +331,7 @@ var CNSStimulation = class {
269
331
  } else {
270
332
  return () => {
271
333
  this.markNeuronInactive(neuron2.name);
272
- return this.processResponse(
334
+ return this.processResponseOrResponses(
273
335
  inputSignal,
274
336
  response
275
337
  );
@@ -278,12 +340,25 @@ var CNSStimulation = class {
278
340
  };
279
341
  return this.instanceNeuronQueue.run(neuron2, starter);
280
342
  }
343
+ processResponseOrResponses(inputSignal, outputSignalOrSignals, error) {
344
+ if (Array.isArray(outputSignalOrSignals)) {
345
+ if (outputSignalOrSignals.length === 0) {
346
+ this.processResponse(inputSignal, void 0, error);
347
+ return;
348
+ }
349
+ for (const signal of outputSignalOrSignals) {
350
+ this.processResponse(inputSignal, signal, error);
351
+ }
352
+ return;
353
+ }
354
+ this.processResponse(inputSignal, outputSignalOrSignals, error);
355
+ }
281
356
  processResponse(inputSignal, outputSignal, error) {
282
- const collateral2 = outputSignal?.collateral;
283
- const ownerNeuron = collateral2 ? this.cns.getParentNeuronByCollateralName(collateral2.name) : void 0;
284
- const subscribers = collateral2 ? this.cns.getSubscribers(collateral2.name) : [];
357
+ const collateralName = outputSignal?.collateralName;
358
+ const ownerNeuron = collateralName ? this.cns.getParentNeuronByCollateralName(collateralName) : void 0;
359
+ const subscribers = collateralName ? this.cns.getSubscribers(collateralName) : [];
285
360
  const subscriberQueueItems = [];
286
- if (collateral2 && !error) {
361
+ if (collateralName && !error) {
287
362
  if (ownerNeuron) {
288
363
  this.cleanupCtxIfNeeded(ownerNeuron);
289
364
  }
@@ -294,28 +369,63 @@ var CNSStimulation = class {
294
369
  }
295
370
  }
296
371
  this.scheduledCount += subscriberQueueItems.length;
297
- this.options?.onResponse?.({
298
- inputSignal,
299
- outputSignal,
300
- ctx: this.ctx,
301
- queueLength: this.queue.length + this.scheduledCount,
302
- stimulationId: this.id,
303
- hops: this.options?.maxNeuronHops && ownerNeuron ? this.nueronVisitMap?.get(ownerNeuron.name) ?? 0 : void 0,
304
- error
305
- });
372
+ let maybePromise;
373
+ try {
374
+ maybePromise = this.options?.onResponse?.({
375
+ inputSignal,
376
+ outputSignal,
377
+ ctx: this.ctx,
378
+ queueLength: this.queue.length + this.scheduledCount,
379
+ stimulationId: this.id,
380
+ hops: this.options?.maxNeuronHops && ownerNeuron ? this.nueronVisitMap?.get(ownerNeuron.name) ?? 0 : void 0,
381
+ error
382
+ });
383
+ } catch (e) {
384
+ this.rejectCompleted(e);
385
+ return;
386
+ }
387
+ if (maybePromise && typeof maybePromise.then === "function") {
388
+ maybePromise.then(
389
+ () => {
390
+ for (let i = 0; i < subscriberQueueItems.length; i++) {
391
+ this.scheduledCount--;
392
+ this.queue.enqueue(subscriberQueueItems[i]);
393
+ }
394
+ this.tryResolveCompleted();
395
+ },
396
+ (error2) => {
397
+ this.rejectCompleted(error2);
398
+ }
399
+ );
400
+ return;
401
+ }
306
402
  for (let i = 0; i < subscriberQueueItems.length; i++) {
307
403
  this.scheduledCount--;
308
404
  this.queue.enqueue(subscriberQueueItems[i]);
309
405
  }
406
+ this.tryResolveCompleted();
310
407
  }
311
- responseToSignal(signal) {
408
+ responseToSignal(signalOrSignals) {
409
+ if (Array.isArray(signalOrSignals)) {
410
+ for (const signal of signalOrSignals) {
411
+ const ownerNeuron2 = this.cns.getParentNeuronByCollateralName(
412
+ signal.collateralName
413
+ );
414
+ if (ownerNeuron2) {
415
+ this.markNeuronActive(ownerNeuron2.name);
416
+ }
417
+ }
418
+ this.processResponseOrResponses(void 0, signalOrSignals);
419
+ return;
420
+ }
312
421
  const ownerNeuron = this.cns.getParentNeuronByCollateralName(
313
- signal.collateral.name
422
+ signalOrSignals.collateralName
314
423
  );
315
424
  if (ownerNeuron) {
316
425
  this.markNeuronActive(ownerNeuron.name);
317
426
  }
318
- this.processResponse(void 0, signal);
427
+ this.processResponse(void 0, signalOrSignals);
428
+ this.tryResolveCompleted();
319
429
  }
320
430
  };
321
431
 
@@ -551,16 +661,30 @@ var CNS = class {
551
661
  };
552
662
  }
553
663
  return (r) => {
554
- try {
555
- if (local) local(r);
556
- } finally {
557
- for (let i = 0; i < this.globalResponseListeners.length; i++) {
558
- try {
559
- this.globalResponseListeners[i](r);
560
- } catch (e) {
561
- }
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);
562
678
  }
563
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
+ }
564
688
  };
565
689
  }
566
690
  runWithNeuronConcurrency(neuron2, fn) {
@@ -840,16 +964,25 @@ var CNS = class {
840
964
  getNeurons() {
841
965
  return Array.from(this.neuronIndex.values());
842
966
  }
967
+ getCollateralByName(collateralName) {
968
+ return this.collateralIndex.get(
969
+ collateralName
970
+ );
971
+ }
972
+ getNeuronByName(neuronName) {
973
+ return this.neuronIndex.get(neuronName);
974
+ }
843
975
  getSubscribers(collateralName) {
844
976
  return this.subIndex.get(collateralName) ?? [];
845
977
  }
846
- stimulate(signal, options) {
978
+ stimulate(signalOrSignals, options) {
847
979
  const wrapped = this.wrapOnResponse(options?.onResponse);
848
980
  const stimulation = new CNSStimulation(this, this.instanceNeuronQueue, {
849
981
  ...options,
850
982
  onResponse: wrapped
851
983
  });
852
- stimulation.responseToSignal(signal);
984
+ stimulation.responseToSignal(signalOrSignals);
985
+ return stimulation.waitUntilComplete();
853
986
  }
854
987
  };
855
988
 
@@ -859,7 +992,7 @@ var CNSCollateral = class {
859
992
  this.name = name;
860
993
  }
861
994
  createSignal(payload) {
862
- return { collateral: this, payload };
995
+ return { collateralName: this.name, payload };
863
996
  }
864
997
  };
865
998
 
@@ -872,6 +1005,10 @@ var neuron = (name, axon) => {
872
1005
  api.concurrency = n;
873
1006
  return api;
874
1007
  },
1008
+ setMaxDuration: (ms) => {
1009
+ api.maxDuration = ms;
1010
+ return api;
1011
+ },
875
1012
  bind: ((axon2, newDendrites) => {
876
1013
  for (const key in newDendrites) {
877
1014
  const value = newDendrites[key];
@@ -888,6 +1025,7 @@ var neuron = (name, axon) => {
888
1025
  axon,
889
1026
  name,
890
1027
  concurrency: void 0,
1028
+ maxDuration: void 0,
891
1029
  dendrites,
892
1030
  dendrite(s) {
893
1031
  dendrites.push(