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