@cnstra/types 0.2.0 → 0.2.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.
package/dist/index.d.mts CHANGED
@@ -1,4 +1,5 @@
1
1
  type TCNSAfferentPath = {
2
+ readonly index: number;
2
3
  parentAfferentPath?: TCNSAfferentPath;
3
4
  };
4
5
 
@@ -8,6 +9,7 @@ type TCNSSignal<TCollateral extends ICNSCollateral<unknown>> = {
8
9
  };
9
10
 
10
11
  interface ICNSCollateral<TPayload> {
12
+ readonly index: number;
11
13
  createSignal(): TCNSSignal<ICNSCollateral<TPayload>>;
12
14
  createSignal(payload: TPayload): TCNSSignal<ICNSCollateral<TPayload>>;
13
15
  }
@@ -37,14 +39,35 @@ type TCNSAxon<TCollaterals extends Record<string, ICNSCollateral<unknown>> = Rec
37
39
  [K in keyof TCollaterals]: TCollaterals[K];
38
40
  };
39
41
 
42
+ /**
43
+ * A context store: data attached to something the engine is running, in
44
+ * compartments.
45
+ *
46
+ * There are two of them, same shape, different life — the run's
47
+ * (`stimulationContext`, one per stimulation) and the activation's
48
+ * (`activationContext`, one per neuron activation). Same shape on purpose: they
49
+ * are used the same way, serialized the same way, and read the same way.
50
+ *
51
+ * A key is a namespace its writer owns, by convention the package name
52
+ * (`'@cnstra/persist-otel'`), so an integration and your own code cannot
53
+ * overwrite each other. A caller who owns their whole run does not need a prefix.
54
+ *
55
+ * Plain object with string keys rather than a `Map` because this crosses a
56
+ * process boundary: `JSON.stringify` turns a `Map` into `{}` and drops symbol
57
+ * keys entirely, so neither could carry anything. What is portable travels with
58
+ * the serialized frontier; a live handle stays where it was made.
59
+ */
60
+ type TCNSContextStore = Record<string, unknown>;
61
+
40
62
  /**
41
63
  * Allowed return types from a neuron dendrite `response` function.
42
64
  *
43
65
  * Signals route by collateral object identity (not by name).
44
66
  */
45
- type TCNSNeuronResponseReturn<TAxon extends TCNSAxon> = Promise<TCNSSignal<TAxon[keyof TAxon]>> | TCNSSignal<TAxon[keyof TAxon]> | Promise<TCNSSignal<TAxon[keyof TAxon]>[]> | TCNSSignal<TAxon[keyof TAxon]>[] | Promise<void> | void;
67
+ type TCNSNeuronResponseReturn<TAxon extends TCNSAxon> = TCNSSignal<TAxon[keyof TAxon]> | TCNSSignal<TAxon[keyof TAxon]>[] | void | Promise<TCNSSignal<TAxon[keyof TAxon]> | TCNSSignal<TAxon[keyof TAxon]>[] | void>;
46
68
 
47
69
  type TCNSNeuron<TAxonType extends TCNSAxon = TCNSAxon> = {
70
+ readonly index: number;
48
71
  axon: TAxonType;
49
72
  /** Optional per-neuron concurrency limit. If undefined or <= 0, no limit is applied. */
50
73
  concurrency?: number;
@@ -62,6 +85,7 @@ type TCNSNeuron<TAxonType extends TCNSAxon = TCNSAxon> = {
62
85
  type TCNSOptions = Record<never, never>;
63
86
 
64
87
  type TCNSModality = {
88
+ readonly index: number;
65
89
  afferentPaths: Record<string | number, TCNSAfferentPath>;
66
90
  };
67
91
 
@@ -69,6 +93,20 @@ type TCNSNeuronActivationTask<TNeuron extends object = object, TCollateral exten
69
93
  neuron: TNeuron;
70
94
  dendriteCollateral: TCollateral;
71
95
  input?: TCNSSignal<TCollateral>;
96
+ /**
97
+ * Data attached to THIS activation, in namespaced compartments — the
98
+ * per-activation sibling of `stimulationContext`.
99
+ *
100
+ * A response listener can write here before the activation runs (the engine
101
+ * builds these tasks first and hands them to the listener on the response),
102
+ * and the dendrite reads them back as `ctx.activationContext`.
103
+ *
104
+ * Compartment keys are strings, by convention the writer's package name, so
105
+ * an integration and your own code cannot overwrite each other. What is
106
+ * portable travels with the serialized frontier; a live handle does not —
107
+ * see `portable()` in `@cnstra/persist`.
108
+ */
109
+ activationContext?: TCNSContextStore;
72
110
  };
73
111
 
74
112
  type TCNSNeuronActivationTaskFailure<TNeuron extends object = object> = {
@@ -98,42 +136,29 @@ interface ICNSStimulation<TNeuron extends TCNSNeuron<any>, TDendrite extends TCN
98
136
  }
99
137
 
100
138
  /**
101
- * Announced when a stimulation's synchronous turn ends: everything reachable
102
- * without yielding to the event loop has run, and what remains (if anything) is
103
- * waiting on a promise or blocked by a concurrency limit.
139
+ * Announced when a stimulation's synchronous turn ends — the batch boundary — or, for a turn inside `cns.batch`, when that batch ends.
104
140
  *
105
- * This is the batch boundary. It is a property of the scheduler loop exhausting,
106
- * not of any individual activation starting or finishing - which is why it cannot
107
- * be derived from `onResponse`. A turn ends in six ways, and a response is only
108
- * emitted in the first:
141
+ * It is a property of the scheduler loop exhausting, not of an activation
142
+ * finishing, so it cannot be derived from `onResponse`: a turn also ends when the
143
+ * last activation returned a promise, when a concurrency limit is reached, on
144
+ * abort, and behind an async listener. Fires once per turn, the final one
145
+ * included.
109
146
  *
110
- * 1. an activation finished and left the queue empty
111
- * 2. the last activation *started* and returned a promise
112
- * 3. the stimulation-level `concurrency` limit was reached
113
- * 4. the stimulation was aborted
114
- * 5. subscribers are parked behind an async `onResponse` listener
115
- * 6. a per-neuron `setConcurrency` gate forced an activation onto a promise
116
- *
117
- * Fires exactly once per turn, including the final one - so a listener that
118
- * flushes on every drain needs no separate completion handling.
119
- *
120
- * The counters carry the same meaning as on `TCNSStimulationResponse`, but they
121
- * are measured *after* the loop exhausted rather than before the work runs. On a
122
- * response `pendingActivations` is a forecast; here it is a fact, and a non-zero
123
- * value can only mean blocked - by a concurrency limit or by an abort - never
124
- * "about to run".
147
+ * The counters mean what they do on `TCNSStimulationResponse`, but measured after
148
+ * the loop exhausted — so a non-zero `pendingActivations` here is blocked, not
149
+ * about to run.
125
150
  */
126
151
  type TCNSStimulationDrain = {
127
152
  stimulation: ICNSStimulation<any, any>;
128
153
  /** `pendingActivations + activeActivations`. Zero means the stimulation is finished. */
129
154
  queueLength: number;
130
- /** Activations whose dendrite body has not been invoked. Non-zero here means blocked. */
155
+ /** Activations whose dendrite response has not been invoked. Non-zero here means blocked. */
131
156
  pendingActivations: number;
132
157
  /** Activations awaiting an unsettled promise. Non-zero means another drain is coming. */
133
158
  activeActivations: number;
134
159
  };
135
160
 
136
- type TCNSStimulationOptions<TResponse, TStimulationContext extends Object = {}> = {
161
+ type TCNSStimulationOptions<TResponse> = {
137
162
  maxNeuronHops?: number;
138
163
  onResponse?: (response: TResponse) => void | Promise<void>;
139
164
  /**
@@ -157,7 +182,14 @@ type TCNSStimulationOptions<TResponse, TStimulationContext extends Object = {}>
157
182
  concurrency?: number;
158
183
  modality?: TCNSModality;
159
184
  afferentPath?: TCNSAfferentPath;
160
- stimulationContext?: TStimulationContext;
185
+ /**
186
+ * Data attached to the whole run, in namespaced compartments — the same
187
+ * object on every hop. Its per-activation sibling is `activationContext`.
188
+ *
189
+ * What is portable travels with the serialized frontier, so a run resumed in
190
+ * another process reads back what the previous attempt left here.
191
+ */
192
+ stimulationContext?: TCNSContextStore;
161
193
  };
162
194
 
163
195
  type TCNSStimulationResponse<TInputCollateral extends ICNSCollateral<unknown> = ICNSCollateral<unknown>, TOutputCollateral extends ICNSCollateral<unknown> = ICNSCollateral<unknown>> = {
@@ -188,17 +220,17 @@ type TCNSStimulationResponse<TInputCollateral extends ICNSCollateral<unknown> =
188
220
  */
189
221
  queueLength: number;
190
222
  /**
191
- * Activations that exist (or are reserved) but whose dendrite body has not
192
- * been invoked yet: waiting in the ring buffer, created and held before being
223
+ * Activations that exist (or are reserved) but whose dendrite response has
224
+ * not been invoked yet: waiting in the ring buffer, created and held before being
193
225
  * enqueued, or reserved by an array fan-out that is still mid-dispatch.
194
226
  *
195
227
  * Subscribers parked behind an async `onResponse` listener are counted here,
196
228
  * not in {@link activeActivations}: the split is by lifecycle stage - whether
197
- * the body has started - not by whether something is being awaited.
229
+ * the response has started - not by whether something is being awaited.
198
230
  */
199
231
  pendingActivations: number;
200
232
  /**
201
- * Activations whose dendrite body has been invoked and returned a promise
233
+ * Activations whose dendrite response has been invoked and returned a promise
202
234
  * that has not settled.
203
235
  *
204
236
  * A synchronous body passes through this state within a single pump
@@ -216,6 +248,46 @@ type TCNSStimulationResponse<TInputCollateral extends ICNSCollateral<unknown> =
216
248
  neuron?: TCNSNeuron<any>;
217
249
  error?: Error;
218
250
  hops?: number;
251
+ /**
252
+ * When the activation's dendrite response was invoked, in epoch milliseconds.
253
+ *
254
+ * Present for real neuron activations on every path — success, sync throw and
255
+ * async rejection alike. Absent on responses that are not an activation (the
256
+ * entry-signal injection, and the terminal signal emitted on abort).
257
+ */
258
+ startedAt?: number;
259
+ /**
260
+ * How long the dendrite response took, in milliseconds: the span from
261
+ * {@link startedAt} to the moment the activation settled.
262
+ *
263
+ * This is the only place per-hop latency exists — it is what a trace needs to
264
+ * draw a waterfall and what a metrics layer needs for a latency histogram.
265
+ * Present exactly when {@link startedAt} is.
266
+ */
267
+ duration?: number;
268
+ /**
269
+ * The activations this response is about to create — built, but not yet run.
270
+ *
271
+ * The engine resolves the subscribers of the output collateral and builds
272
+ * their tasks BEFORE handing the response to a listener, then enqueues those
273
+ * very objects afterwards — on the synchronous path, after an async listener
274
+ * settles, and even after a listener throws. So a listener can write into
275
+ * `task.activationContext` here and the dendrite will read it back as
276
+ * `ctx.activationContext`.
277
+ *
278
+ * Empty on a response that starts nothing: a sink neuron, a failure, the
279
+ * entry injection with no subscribers, the terminal abort signal.
280
+ */
281
+ scheduledActivationTasks?: readonly TCNSNeuronActivationTask<any>[];
282
+ /**
283
+ * The context store of the activation that produced this response — the same
284
+ * object its own handler reads as `ctx.activationContext`.
285
+ *
286
+ * Here so a recorder can put it in the task's history row without holding the
287
+ * task itself. Undefined when nothing was attached, and on responses that are
288
+ * not an activation.
289
+ */
290
+ activationContext?: TCNSContextStore;
219
291
  };
220
292
 
221
293
  type TCNSSubscriber<TNeuron extends TCNSNeuron<any>, TDendrite extends TCNSDendrite<any, any>> = {
@@ -248,10 +320,11 @@ interface ICNS<TNeuron extends TCNSNeuron<any>, TDendrite extends TCNSDendrite<a
248
320
  addResponseListener(listener: (response: TCNSStimulationResponse) => void): () => void;
249
321
  /**
250
322
  * Add a global batch-boundary listener applied to all stimulations, called
251
- * once at the end of every synchronous turn.
323
+ * once at the end of every synchronous turn (inside `batch`, when the batch ends).
252
324
  * Returns an unsubscribe function.
253
325
  */
254
326
  addDrainListener(listener: (drain: TCNSStimulationDrain) => void): () => void;
327
+ batch<TResult>(run: () => TResult): TResult;
255
328
  stimulate(signalOrSignals: TCNSSignal<ICNSCollateral<unknown>> | TCNSSignal<ICNSCollateral<unknown>>[], options?: TCNSStimulationOptions<TCNSStimulationResponse>): ICNSStimulation<TNeuron, TDendrite>;
256
329
  /**
257
330
  * Resume a run from a set of activation tasks (e.g. an outstanding frontier
@@ -276,14 +349,22 @@ type TCNSHandlerContextBase = {
276
349
  cns?: ICNS<any, any>;
277
350
  stimulation?: any;
278
351
  /**
279
- * The caller-supplied value passed as `stimulate(sig, { stimulationContext })`
280
- * (or `activate(...)`) — the same object on every hop of this stimulation, in
281
- * any environment (sync or async, node or browser). Use it to correlate logs
282
- * to a run: pass `{ stimulationId, attemptNumber }` and read it here. Typed
283
- * `unknown` because the neuron doesn't own the shape — the caller does; cast at
284
- * the read site.
352
+ * The context store of the whole run — what the caller passed as
353
+ * `stimulate(sig, { stimulationContext })` plus whatever the run has written
354
+ * into it since, the same object on every hop in any environment (sync or
355
+ * async, node or browser). Keyed by namespace; the values inside are
356
+ * `unknown`, so cast at the read site.
285
357
  */
286
- stimulationContext?: unknown;
358
+ stimulationContext?: TCNSContextStore;
359
+ /**
360
+ * The context store of the activation currently running — data a response
361
+ * listener attached to THIS activation before it started. Unset when nobody
362
+ * wrote anything.
363
+ *
364
+ * Same shape as {@link stimulationContext}, shorter life: this one is gone
365
+ * when the activation returns.
366
+ */
367
+ activationContext?: TCNSContextStore;
287
368
  };
288
369
  /**
289
370
  * The `ctx` a dendrite handler receives. `TExt` is whatever the factory layers
@@ -301,17 +382,4 @@ type TCNSDendrite<TSenderCollateral extends ICNSCollateral<unknown>, TAxonType e
301
382
  response: (payload: TSenderCollateral extends ICNSCollateral<infer P> ? P : never, axon: TAxonType, ctx: TCNSHandlerContext<TExt>) => TCNSNeuronResponseReturn<TAxonType>;
302
383
  };
303
384
 
304
- type CNSCollateralLike = ICNSCollateral<unknown>;
305
- type TCNSNeuronPersistOptions<TNeuron extends TCNSNeuron<any> = TCNSNeuron<any>> = {
306
- name: string;
307
- neuron: TNeuron;
308
- };
309
- type TCNSCollateralPersistOptions<TCollateral extends CNSCollateralLike = CNSCollateralLike> = {
310
- name: string;
311
- collateral: TCollateral;
312
- };
313
- type TCNSStimulationPersistOptions = {
314
- stimulationId: string;
315
- };
316
-
317
- export type { ICNS, ICNSCollateral, ICNSNetwork, ICNSStimulation, TCNSAfferentPath, TCNSAxon, TCNSCollateralPersistOptions, TCNSDendrite, TCNSHandlerContext, TCNSModality, TCNSNeuron, TCNSNeuronActivationTask, TCNSNeuronActivationTaskFailure, TCNSNeuronPersistOptions, TCNSNeuronResponseReturn, TCNSOptions, TCNSSignal, TCNSStimulationDrain, TCNSStimulationOptions, TCNSStimulationPersistOptions, TCNSStimulationResponse, TCNSSubscriber };
385
+ export type { ICNS, ICNSCollateral, ICNSNetwork, ICNSStimulation, TCNSAfferentPath, TCNSAxon, TCNSContextStore, TCNSDendrite, TCNSHandlerContext, TCNSModality, TCNSNeuron, TCNSNeuronActivationTask, TCNSNeuronActivationTaskFailure, TCNSNeuronResponseReturn, TCNSOptions, TCNSSignal, TCNSStimulationDrain, TCNSStimulationOptions, TCNSStimulationResponse, TCNSSubscriber };
package/dist/index.d.ts CHANGED
@@ -1,4 +1,5 @@
1
1
  type TCNSAfferentPath = {
2
+ readonly index: number;
2
3
  parentAfferentPath?: TCNSAfferentPath;
3
4
  };
4
5
 
@@ -8,6 +9,7 @@ type TCNSSignal<TCollateral extends ICNSCollateral<unknown>> = {
8
9
  };
9
10
 
10
11
  interface ICNSCollateral<TPayload> {
12
+ readonly index: number;
11
13
  createSignal(): TCNSSignal<ICNSCollateral<TPayload>>;
12
14
  createSignal(payload: TPayload): TCNSSignal<ICNSCollateral<TPayload>>;
13
15
  }
@@ -37,14 +39,35 @@ type TCNSAxon<TCollaterals extends Record<string, ICNSCollateral<unknown>> = Rec
37
39
  [K in keyof TCollaterals]: TCollaterals[K];
38
40
  };
39
41
 
42
+ /**
43
+ * A context store: data attached to something the engine is running, in
44
+ * compartments.
45
+ *
46
+ * There are two of them, same shape, different life — the run's
47
+ * (`stimulationContext`, one per stimulation) and the activation's
48
+ * (`activationContext`, one per neuron activation). Same shape on purpose: they
49
+ * are used the same way, serialized the same way, and read the same way.
50
+ *
51
+ * A key is a namespace its writer owns, by convention the package name
52
+ * (`'@cnstra/persist-otel'`), so an integration and your own code cannot
53
+ * overwrite each other. A caller who owns their whole run does not need a prefix.
54
+ *
55
+ * Plain object with string keys rather than a `Map` because this crosses a
56
+ * process boundary: `JSON.stringify` turns a `Map` into `{}` and drops symbol
57
+ * keys entirely, so neither could carry anything. What is portable travels with
58
+ * the serialized frontier; a live handle stays where it was made.
59
+ */
60
+ type TCNSContextStore = Record<string, unknown>;
61
+
40
62
  /**
41
63
  * Allowed return types from a neuron dendrite `response` function.
42
64
  *
43
65
  * Signals route by collateral object identity (not by name).
44
66
  */
45
- type TCNSNeuronResponseReturn<TAxon extends TCNSAxon> = Promise<TCNSSignal<TAxon[keyof TAxon]>> | TCNSSignal<TAxon[keyof TAxon]> | Promise<TCNSSignal<TAxon[keyof TAxon]>[]> | TCNSSignal<TAxon[keyof TAxon]>[] | Promise<void> | void;
67
+ type TCNSNeuronResponseReturn<TAxon extends TCNSAxon> = TCNSSignal<TAxon[keyof TAxon]> | TCNSSignal<TAxon[keyof TAxon]>[] | void | Promise<TCNSSignal<TAxon[keyof TAxon]> | TCNSSignal<TAxon[keyof TAxon]>[] | void>;
46
68
 
47
69
  type TCNSNeuron<TAxonType extends TCNSAxon = TCNSAxon> = {
70
+ readonly index: number;
48
71
  axon: TAxonType;
49
72
  /** Optional per-neuron concurrency limit. If undefined or <= 0, no limit is applied. */
50
73
  concurrency?: number;
@@ -62,6 +85,7 @@ type TCNSNeuron<TAxonType extends TCNSAxon = TCNSAxon> = {
62
85
  type TCNSOptions = Record<never, never>;
63
86
 
64
87
  type TCNSModality = {
88
+ readonly index: number;
65
89
  afferentPaths: Record<string | number, TCNSAfferentPath>;
66
90
  };
67
91
 
@@ -69,6 +93,20 @@ type TCNSNeuronActivationTask<TNeuron extends object = object, TCollateral exten
69
93
  neuron: TNeuron;
70
94
  dendriteCollateral: TCollateral;
71
95
  input?: TCNSSignal<TCollateral>;
96
+ /**
97
+ * Data attached to THIS activation, in namespaced compartments — the
98
+ * per-activation sibling of `stimulationContext`.
99
+ *
100
+ * A response listener can write here before the activation runs (the engine
101
+ * builds these tasks first and hands them to the listener on the response),
102
+ * and the dendrite reads them back as `ctx.activationContext`.
103
+ *
104
+ * Compartment keys are strings, by convention the writer's package name, so
105
+ * an integration and your own code cannot overwrite each other. What is
106
+ * portable travels with the serialized frontier; a live handle does not —
107
+ * see `portable()` in `@cnstra/persist`.
108
+ */
109
+ activationContext?: TCNSContextStore;
72
110
  };
73
111
 
74
112
  type TCNSNeuronActivationTaskFailure<TNeuron extends object = object> = {
@@ -98,42 +136,29 @@ interface ICNSStimulation<TNeuron extends TCNSNeuron<any>, TDendrite extends TCN
98
136
  }
99
137
 
100
138
  /**
101
- * Announced when a stimulation's synchronous turn ends: everything reachable
102
- * without yielding to the event loop has run, and what remains (if anything) is
103
- * waiting on a promise or blocked by a concurrency limit.
139
+ * Announced when a stimulation's synchronous turn ends — the batch boundary — or, for a turn inside `cns.batch`, when that batch ends.
104
140
  *
105
- * This is the batch boundary. It is a property of the scheduler loop exhausting,
106
- * not of any individual activation starting or finishing - which is why it cannot
107
- * be derived from `onResponse`. A turn ends in six ways, and a response is only
108
- * emitted in the first:
141
+ * It is a property of the scheduler loop exhausting, not of an activation
142
+ * finishing, so it cannot be derived from `onResponse`: a turn also ends when the
143
+ * last activation returned a promise, when a concurrency limit is reached, on
144
+ * abort, and behind an async listener. Fires once per turn, the final one
145
+ * included.
109
146
  *
110
- * 1. an activation finished and left the queue empty
111
- * 2. the last activation *started* and returned a promise
112
- * 3. the stimulation-level `concurrency` limit was reached
113
- * 4. the stimulation was aborted
114
- * 5. subscribers are parked behind an async `onResponse` listener
115
- * 6. a per-neuron `setConcurrency` gate forced an activation onto a promise
116
- *
117
- * Fires exactly once per turn, including the final one - so a listener that
118
- * flushes on every drain needs no separate completion handling.
119
- *
120
- * The counters carry the same meaning as on `TCNSStimulationResponse`, but they
121
- * are measured *after* the loop exhausted rather than before the work runs. On a
122
- * response `pendingActivations` is a forecast; here it is a fact, and a non-zero
123
- * value can only mean blocked - by a concurrency limit or by an abort - never
124
- * "about to run".
147
+ * The counters mean what they do on `TCNSStimulationResponse`, but measured after
148
+ * the loop exhausted — so a non-zero `pendingActivations` here is blocked, not
149
+ * about to run.
125
150
  */
126
151
  type TCNSStimulationDrain = {
127
152
  stimulation: ICNSStimulation<any, any>;
128
153
  /** `pendingActivations + activeActivations`. Zero means the stimulation is finished. */
129
154
  queueLength: number;
130
- /** Activations whose dendrite body has not been invoked. Non-zero here means blocked. */
155
+ /** Activations whose dendrite response has not been invoked. Non-zero here means blocked. */
131
156
  pendingActivations: number;
132
157
  /** Activations awaiting an unsettled promise. Non-zero means another drain is coming. */
133
158
  activeActivations: number;
134
159
  };
135
160
 
136
- type TCNSStimulationOptions<TResponse, TStimulationContext extends Object = {}> = {
161
+ type TCNSStimulationOptions<TResponse> = {
137
162
  maxNeuronHops?: number;
138
163
  onResponse?: (response: TResponse) => void | Promise<void>;
139
164
  /**
@@ -157,7 +182,14 @@ type TCNSStimulationOptions<TResponse, TStimulationContext extends Object = {}>
157
182
  concurrency?: number;
158
183
  modality?: TCNSModality;
159
184
  afferentPath?: TCNSAfferentPath;
160
- stimulationContext?: TStimulationContext;
185
+ /**
186
+ * Data attached to the whole run, in namespaced compartments — the same
187
+ * object on every hop. Its per-activation sibling is `activationContext`.
188
+ *
189
+ * What is portable travels with the serialized frontier, so a run resumed in
190
+ * another process reads back what the previous attempt left here.
191
+ */
192
+ stimulationContext?: TCNSContextStore;
161
193
  };
162
194
 
163
195
  type TCNSStimulationResponse<TInputCollateral extends ICNSCollateral<unknown> = ICNSCollateral<unknown>, TOutputCollateral extends ICNSCollateral<unknown> = ICNSCollateral<unknown>> = {
@@ -188,17 +220,17 @@ type TCNSStimulationResponse<TInputCollateral extends ICNSCollateral<unknown> =
188
220
  */
189
221
  queueLength: number;
190
222
  /**
191
- * Activations that exist (or are reserved) but whose dendrite body has not
192
- * been invoked yet: waiting in the ring buffer, created and held before being
223
+ * Activations that exist (or are reserved) but whose dendrite response has
224
+ * not been invoked yet: waiting in the ring buffer, created and held before being
193
225
  * enqueued, or reserved by an array fan-out that is still mid-dispatch.
194
226
  *
195
227
  * Subscribers parked behind an async `onResponse` listener are counted here,
196
228
  * not in {@link activeActivations}: the split is by lifecycle stage - whether
197
- * the body has started - not by whether something is being awaited.
229
+ * the response has started - not by whether something is being awaited.
198
230
  */
199
231
  pendingActivations: number;
200
232
  /**
201
- * Activations whose dendrite body has been invoked and returned a promise
233
+ * Activations whose dendrite response has been invoked and returned a promise
202
234
  * that has not settled.
203
235
  *
204
236
  * A synchronous body passes through this state within a single pump
@@ -216,6 +248,46 @@ type TCNSStimulationResponse<TInputCollateral extends ICNSCollateral<unknown> =
216
248
  neuron?: TCNSNeuron<any>;
217
249
  error?: Error;
218
250
  hops?: number;
251
+ /**
252
+ * When the activation's dendrite response was invoked, in epoch milliseconds.
253
+ *
254
+ * Present for real neuron activations on every path — success, sync throw and
255
+ * async rejection alike. Absent on responses that are not an activation (the
256
+ * entry-signal injection, and the terminal signal emitted on abort).
257
+ */
258
+ startedAt?: number;
259
+ /**
260
+ * How long the dendrite response took, in milliseconds: the span from
261
+ * {@link startedAt} to the moment the activation settled.
262
+ *
263
+ * This is the only place per-hop latency exists — it is what a trace needs to
264
+ * draw a waterfall and what a metrics layer needs for a latency histogram.
265
+ * Present exactly when {@link startedAt} is.
266
+ */
267
+ duration?: number;
268
+ /**
269
+ * The activations this response is about to create — built, but not yet run.
270
+ *
271
+ * The engine resolves the subscribers of the output collateral and builds
272
+ * their tasks BEFORE handing the response to a listener, then enqueues those
273
+ * very objects afterwards — on the synchronous path, after an async listener
274
+ * settles, and even after a listener throws. So a listener can write into
275
+ * `task.activationContext` here and the dendrite will read it back as
276
+ * `ctx.activationContext`.
277
+ *
278
+ * Empty on a response that starts nothing: a sink neuron, a failure, the
279
+ * entry injection with no subscribers, the terminal abort signal.
280
+ */
281
+ scheduledActivationTasks?: readonly TCNSNeuronActivationTask<any>[];
282
+ /**
283
+ * The context store of the activation that produced this response — the same
284
+ * object its own handler reads as `ctx.activationContext`.
285
+ *
286
+ * Here so a recorder can put it in the task's history row without holding the
287
+ * task itself. Undefined when nothing was attached, and on responses that are
288
+ * not an activation.
289
+ */
290
+ activationContext?: TCNSContextStore;
219
291
  };
220
292
 
221
293
  type TCNSSubscriber<TNeuron extends TCNSNeuron<any>, TDendrite extends TCNSDendrite<any, any>> = {
@@ -248,10 +320,11 @@ interface ICNS<TNeuron extends TCNSNeuron<any>, TDendrite extends TCNSDendrite<a
248
320
  addResponseListener(listener: (response: TCNSStimulationResponse) => void): () => void;
249
321
  /**
250
322
  * Add a global batch-boundary listener applied to all stimulations, called
251
- * once at the end of every synchronous turn.
323
+ * once at the end of every synchronous turn (inside `batch`, when the batch ends).
252
324
  * Returns an unsubscribe function.
253
325
  */
254
326
  addDrainListener(listener: (drain: TCNSStimulationDrain) => void): () => void;
327
+ batch<TResult>(run: () => TResult): TResult;
255
328
  stimulate(signalOrSignals: TCNSSignal<ICNSCollateral<unknown>> | TCNSSignal<ICNSCollateral<unknown>>[], options?: TCNSStimulationOptions<TCNSStimulationResponse>): ICNSStimulation<TNeuron, TDendrite>;
256
329
  /**
257
330
  * Resume a run from a set of activation tasks (e.g. an outstanding frontier
@@ -276,14 +349,22 @@ type TCNSHandlerContextBase = {
276
349
  cns?: ICNS<any, any>;
277
350
  stimulation?: any;
278
351
  /**
279
- * The caller-supplied value passed as `stimulate(sig, { stimulationContext })`
280
- * (or `activate(...)`) — the same object on every hop of this stimulation, in
281
- * any environment (sync or async, node or browser). Use it to correlate logs
282
- * to a run: pass `{ stimulationId, attemptNumber }` and read it here. Typed
283
- * `unknown` because the neuron doesn't own the shape — the caller does; cast at
284
- * the read site.
352
+ * The context store of the whole run — what the caller passed as
353
+ * `stimulate(sig, { stimulationContext })` plus whatever the run has written
354
+ * into it since, the same object on every hop in any environment (sync or
355
+ * async, node or browser). Keyed by namespace; the values inside are
356
+ * `unknown`, so cast at the read site.
285
357
  */
286
- stimulationContext?: unknown;
358
+ stimulationContext?: TCNSContextStore;
359
+ /**
360
+ * The context store of the activation currently running — data a response
361
+ * listener attached to THIS activation before it started. Unset when nobody
362
+ * wrote anything.
363
+ *
364
+ * Same shape as {@link stimulationContext}, shorter life: this one is gone
365
+ * when the activation returns.
366
+ */
367
+ activationContext?: TCNSContextStore;
287
368
  };
288
369
  /**
289
370
  * The `ctx` a dendrite handler receives. `TExt` is whatever the factory layers
@@ -301,17 +382,4 @@ type TCNSDendrite<TSenderCollateral extends ICNSCollateral<unknown>, TAxonType e
301
382
  response: (payload: TSenderCollateral extends ICNSCollateral<infer P> ? P : never, axon: TAxonType, ctx: TCNSHandlerContext<TExt>) => TCNSNeuronResponseReturn<TAxonType>;
302
383
  };
303
384
 
304
- type CNSCollateralLike = ICNSCollateral<unknown>;
305
- type TCNSNeuronPersistOptions<TNeuron extends TCNSNeuron<any> = TCNSNeuron<any>> = {
306
- name: string;
307
- neuron: TNeuron;
308
- };
309
- type TCNSCollateralPersistOptions<TCollateral extends CNSCollateralLike = CNSCollateralLike> = {
310
- name: string;
311
- collateral: TCollateral;
312
- };
313
- type TCNSStimulationPersistOptions = {
314
- stimulationId: string;
315
- };
316
-
317
- export type { ICNS, ICNSCollateral, ICNSNetwork, ICNSStimulation, TCNSAfferentPath, TCNSAxon, TCNSCollateralPersistOptions, TCNSDendrite, TCNSHandlerContext, TCNSModality, TCNSNeuron, TCNSNeuronActivationTask, TCNSNeuronActivationTaskFailure, TCNSNeuronPersistOptions, TCNSNeuronResponseReturn, TCNSOptions, TCNSSignal, TCNSStimulationDrain, TCNSStimulationOptions, TCNSStimulationPersistOptions, TCNSStimulationResponse, TCNSSubscriber };
385
+ export type { ICNS, ICNSCollateral, ICNSNetwork, ICNSStimulation, TCNSAfferentPath, TCNSAxon, TCNSContextStore, TCNSDendrite, TCNSHandlerContext, TCNSModality, TCNSNeuron, TCNSNeuronActivationTask, TCNSNeuronActivationTaskFailure, TCNSNeuronResponseReturn, TCNSOptions, TCNSSignal, TCNSStimulationDrain, TCNSStimulationOptions, TCNSStimulationResponse, TCNSSubscriber };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./types/TCNSAfferentPath\";\nexport * from \"./types/TCNSAxon\";\nexport * from \"./types/TCNSDendrite\";\nexport * from \"./types/TCNSHandlerContext\";\nexport * from \"./types/TCNSModality\";\nexport * from \"./types/TCNSNeuron\";\nexport * from \"./types/TCNSNeuronActivationTask\";\nexport * from \"./types/TCNSNeuronActivationTaskFailure\";\nexport * from \"./types/TCNSNeuronResponseReturn\";\nexport * from \"./types/TCNSOptions\";\nexport * from \"./types/TCNSPersist\";\nexport * from \"./types/TCNSSignal\";\nexport * from \"./types/TCNSStimulationDrain\";\nexport * from \"./types/TCNSStimulationOptions\";\nexport * from \"./types/TCNSStimulationResponse\";\nexport * from \"./types/TCNSSubscriber\";\nexport * from \"./interfaces/ICNS\";\nexport * from \"./interfaces/ICNSCollateral\";\nexport * from \"./interfaces/ICNSNetwork\";\nexport * from \"./interfaces/ICNSStimulation\";\n"],"mappings":";;;;;;;;;;;;;;;AAAA;;","names":[]}
1
+ {"version":3,"sources":["../src/index.ts"],"sourcesContent":["export * from \"./types/TCNSAfferentPath\";\nexport * from \"./types/TCNSAxon\";\nexport * from \"./types/TCNSContextStore\";\nexport * from \"./types/TCNSDendrite\";\nexport * from \"./types/TCNSHandlerContext\";\nexport * from \"./types/TCNSModality\";\nexport * from \"./types/TCNSNeuron\";\nexport * from \"./types/TCNSNeuronActivationTask\";\nexport * from \"./types/TCNSNeuronActivationTaskFailure\";\nexport * from \"./types/TCNSNeuronResponseReturn\";\nexport * from \"./types/TCNSOptions\";\nexport * from \"./types/TCNSSignal\";\nexport * from \"./types/TCNSStimulationDrain\";\nexport * from \"./types/TCNSStimulationOptions\";\nexport * from \"./types/TCNSStimulationResponse\";\nexport * from \"./types/TCNSSubscriber\";\nexport * from \"./interfaces/ICNS\";\nexport * from \"./interfaces/ICNSCollateral\";\nexport * from \"./interfaces/ICNSNetwork\";\nexport * from \"./interfaces/ICNSStimulation\";\n"],"mappings":";;;;;;;;;;;;;;;AAAA;;","names":[]}
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@cnstra/types",
3
- "version": "0.2.0",
3
+ "version": "0.2.1",
4
4
  "description": "Shared TypeScript contract for CNStra: signals, tasks, axons, neuron/collateral/stimulation/network interfaces. Zero runtime — the base package core and integrations build on.",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "git+https://github.com/abaikov/cnstra.git",
8
+ "directory": "packages/types"
9
+ },
5
10
  "keywords": ["cnstra", "types", "contract", "interfaces"],
6
11
  "main": "dist/index.js",
7
12
  "module": "dist/index.mjs",