@cnstra/core 1.8.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.
- package/README.md +14 -609
- package/dist/CNSStimulation.d.ts.map +1 -1
- package/dist/index.cjs +1 -1
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/types/TCNSStimulationOptions.d.ts +2 -0
- package/dist/types/TCNSStimulationOptions.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,49 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
**Graph-routed, type-safe orchestration for reactive apps — no global event bus.**
|
|
4
4
|
|
|
5
|
-
📚 **[Full Documentation](https://cnstra.org/)** | [Quick Start](https://cnstra.org/docs/core/quick-start) | [API Reference](https://cnstra.org/docs/core/api) | [Recipes](https://cnstra.org/docs/recipes
|
|
5
|
+
📚 **[Full Documentation →](https://cnstra.org/)** | [Quick Start](https://cnstra.org/docs/core/quick-start) | [API Reference](https://cnstra.org/docs/core/api) | [Recipes](https://cnstra.org/docs/recipes)
|
|
6
6
|
|
|
7
|
-
##
|
|
7
|
+
## What is CNStra?
|
|
8
8
|
|
|
9
|
-
**CNStra (Central Nervous System Orchestrator)** models your app as a **typed neuron graph**.
|
|
10
|
-
You explicitly start a run with `cns.stimulate(...)`; CNStra then performs a **deterministic, hop-bounded traversal** from **collateral → dendrite → returned signal**, step by step.
|
|
9
|
+
**CNStra (Central Nervous System Orchestrator)** models your app as a **typed neuron graph**. You explicitly start a run with `cns.stimulate(...)`; CNStra then performs a **deterministic, hop-bounded traversal** from **collateral → dendrite → returned signal**, step by step.
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
**Zero dependencies** • **No pub/sub** • **CNS approach** (Central Neural Network of your app)
|
|
13
12
|
|
|
14
|
-
|
|
13
|
+
👉 **[Read the full documentation →](https://cnstra.org/)**
|
|
15
14
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
## 💡 Why CNStra
|
|
19
|
-
|
|
20
|
-
IERG is our inverted control flow: you start the run, we walk the reaction graph explicitly. This is not Flux and not an event bus.
|
|
21
|
-
|
|
22
|
-
- **Deterministic routing**: signals are delivered along an explicit neuron graph, not broadcast to whoever “happens to listen”.
|
|
23
|
-
- **Readable, reliable flows**: each step is local and typed; branches are explicit, so debugging feels like reading a storyboard, not a log stream.
|
|
24
|
-
- **Backpressure & concurrency**: built‑in per‑stimulation and per‑neuron concurrency limits keep workloads controlled without custom plumbing.
|
|
25
|
-
- **Saga‑grade orchestration**: IERG already models long‑running, multi‑step reactions with retries/cancellation hooks (abort), so you rarely need to hand‑roll “sagas”.
|
|
26
|
-
- **Safer than ad‑hoc events**: no hidden global listeners, no accidental fan‑out; every continuation must be returned explicitly.
|
|
27
|
-
|
|
28
|
-
## 🏗️ Core Model
|
|
29
|
-
|
|
30
|
-
### Neurons
|
|
31
|
-
Units of logic with clear DI and sharp boundaries:
|
|
32
|
-
- **ID** — unique name
|
|
33
|
-
- **Axon** — the neuron's **output channels** (its collaterals)
|
|
34
|
-
- **Dendrites** — **input receptors** (typed reactions bound to specific collaterals)
|
|
35
|
-
|
|
36
|
-
### Collaterals
|
|
37
|
-
Typed **output channels** that mint signals:
|
|
38
|
-
- **ID** — string identifier (e.g., `'user:created'`)
|
|
39
|
-
- **Payload** — the shape carried by the signal
|
|
40
|
-
- `createSignal(payload)` → `{ collateral, payload }`
|
|
41
|
-
|
|
42
|
-
### Signals
|
|
43
|
-
The data structures that flow through the system:
|
|
44
|
-
- **collateral** — reference to the collateral that created this signal
|
|
45
|
-
- **payload** — the typed data being transmitted
|
|
46
|
-
|
|
47
|
-
## 🚀 Quick Start
|
|
15
|
+
## Quick Start
|
|
48
16
|
|
|
49
17
|
```bash
|
|
50
18
|
npm install @cnstra/core
|
|
@@ -64,14 +32,8 @@ const userService = neuron('user-service', {
|
|
|
64
32
|
.dendrite({
|
|
65
33
|
collateral: userCreated,
|
|
66
34
|
response: (payload, axon) => {
|
|
67
|
-
const userData = payload;
|
|
68
|
-
|
|
69
|
-
// Process the user creation
|
|
70
|
-
console.log(`Processing user: ${userData.name}`);
|
|
71
|
-
|
|
72
|
-
// Return the signal that will be processed by CNS
|
|
73
35
|
return axon.userRegistered.createSignal({
|
|
74
|
-
userId:
|
|
36
|
+
userId: payload.id,
|
|
75
37
|
status: 'completed'
|
|
76
38
|
});
|
|
77
39
|
}
|
|
@@ -88,571 +50,14 @@ const stimulation = cns.stimulate(userCreated.createSignal({
|
|
|
88
50
|
await stimulation.waitUntilComplete();
|
|
89
51
|
```
|
|
90
52
|
|
|
91
|
-
##
|
|
92
|
-
|
|
93
|
-
### `collateral<T>(id: string)`
|
|
94
|
-
|
|
95
|
-
Creates a new collateral (communication channel).
|
|
96
|
-
|
|
97
|
-
```typescript
|
|
98
|
-
const userEvent = collateral<{ userId: string }>('user:event');
|
|
99
|
-
const simpleEvent = collateral('simple:event'); // No payload type
|
|
100
|
-
```
|
|
101
|
-
|
|
102
|
-
### `neuron(id: string, axon: Axon)`
|
|
103
|
-
|
|
104
|
-
Creates a new neuron with the specified axon (output channels).
|
|
105
|
-
|
|
106
|
-
```typescript
|
|
107
|
-
const myNeuron = neuron('my-neuron', {
|
|
108
|
-
output: myCollateral
|
|
109
|
-
});
|
|
110
|
-
```
|
|
111
|
-
|
|
112
|
-
### `neuron.dendrite(dendrite: Dendrite)`
|
|
113
|
-
|
|
114
|
-
Adds a dendrite (input receptor) to a neuron. Returns the neuron for chaining.
|
|
115
|
-
|
|
116
|
-
The `response` function can return:
|
|
117
|
-
- A single signal: `axon.output.createSignal(data)`
|
|
118
|
-
- An array of signals: `[axon.output1.createSignal(data1), axon.output2.createSignal(data2)]`
|
|
119
|
-
- A Promise of either
|
|
120
|
-
- `undefined`/`void` to end processing
|
|
121
|
-
|
|
122
|
-
```typescript
|
|
123
|
-
myNeuron
|
|
124
|
-
.dendrite({
|
|
125
|
-
collateral: inputCollateral,
|
|
126
|
-
response: async (payload, axon, ctx) => {
|
|
127
|
-
// ctx: { get, set, abortSignal?, cns? }
|
|
128
|
-
if (ctx.abortSignal?.aborted) return; // graceful cancel
|
|
129
|
-
const prev = ctx.get();
|
|
130
|
-
ctx.set({ ...prev, handled: true });
|
|
131
|
-
|
|
132
|
-
// Return single signal or array of signals
|
|
133
|
-
return axon.output.createSignal(result);
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
```
|
|
137
|
-
|
|
138
|
-
### `neuron.bind(axon, map)` — exhaustive subscriptions (with shorthand)
|
|
139
|
-
|
|
140
|
-
Bind this neuron to every collateral of another neuron's axon in one place. The `map` must be exhaustive: you must provide a handler for each collateral key of `axon`. This gives you compile-time safety: if someone adds a new collateral later, TypeScript will immediately flag missing handlers.
|
|
141
|
-
|
|
142
|
-
- You can pass either a full dendrite object per key or just a response function shorthand.
|
|
143
|
-
- Payload types are inferred from the followed axon.
|
|
144
|
-
|
|
145
|
-
This pattern is especially useful for domain-oriented neurons that must react to every way a record can be created/changed. For example, an email-notifier neuron can safely ensure emails are sent for every creation path; if a new creation collateral is introduced, the build will fail until the notifier adds a corresponding handler.
|
|
146
|
-
|
|
147
|
-
```typescript
|
|
148
|
-
import { withCtx, collateral } from '@cnstra/core';
|
|
149
|
-
|
|
150
|
-
// Order domain model (axon)
|
|
151
|
-
const order = {
|
|
152
|
-
created: collateral<{ id: string; amount: number }>('order:created'),
|
|
153
|
-
updated: collateral<{ id: string; changes: Record<string, unknown> }>('order:updated'),
|
|
154
|
-
cancelled: collateral<{ id: string; reason?: string }>('order:cancelled'),
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
// Mailer neuron must react to ALL order events
|
|
158
|
-
withCtx()
|
|
159
|
-
.neuron('order-mailer', { /* your axon if you emit follow-up signals */ })
|
|
160
|
-
.bind(order, {
|
|
161
|
-
created: (payload) => {
|
|
162
|
-
sendEmail(`Order created #${payload.id} for $${payload.amount}`);
|
|
163
|
-
return undefined;
|
|
164
|
-
},
|
|
165
|
-
updated: (payload) => {
|
|
166
|
-
sendEmail(`Order updated #${payload.id} (changes: ${Object.keys(payload.changes).join(', ')})`);
|
|
167
|
-
return undefined;
|
|
168
|
-
},
|
|
169
|
-
cancelled: (payload) => {
|
|
170
|
-
sendEmail(`Order cancelled #${payload.id}${payload.reason ? `: ${payload.reason}` : ''}`);
|
|
171
|
-
return undefined;
|
|
172
|
-
},
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// If later someone adds a new event variant, e.g. refunds:
|
|
176
|
-
// const order = { ...order, refunded: collateral<{ id: string; amount: number }>('order:refunded') };
|
|
177
|
-
// TypeScript will now error until you also add a `refunded` handler in the .bind(...) map.
|
|
178
|
-
```
|
|
179
|
-
|
|
180
|
-
### `CNS` Class
|
|
181
|
-
|
|
182
|
-
The main orchestrator that manages signal flow between neurons.
|
|
183
|
-
|
|
184
|
-
#### Constructor
|
|
185
|
-
```typescript
|
|
186
|
-
new CNS(neurons, options?)
|
|
187
|
-
```
|
|
188
|
-
|
|
189
|
-
**Parameters:**
|
|
190
|
-
- `neurons`: Array of neurons that process signals
|
|
191
|
-
- `options`: Optional CNS configuration
|
|
192
|
-
|
|
193
|
-
#### Global listeners
|
|
194
|
-
```typescript
|
|
195
|
-
const unsubscribe = cns.addResponseListener(r => {
|
|
196
|
-
// fires for every stimulation (input + outputs)
|
|
197
|
-
});
|
|
198
|
-
// unsubscribe();
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
#### `stimulate()` Method
|
|
202
|
-
```typescript
|
|
203
|
-
cns.stimulate(signal | signal[], options?)
|
|
204
|
-
```
|
|
205
|
-
|
|
206
|
-
**Parameters:**
|
|
207
|
-
- `signal`: A signal or array of signals created by `collateral.createSignal(payload)`
|
|
208
|
-
- `options`: Optional stimulation configuration
|
|
209
|
-
|
|
210
|
-
**Returns:** `CNSStimulation` instance. Use `stimulation.waitUntilComplete()` to await completion.
|
|
211
|
-
|
|
212
|
-
**Example:**
|
|
213
|
-
```typescript
|
|
214
|
-
// Single signal
|
|
215
|
-
const stimulation = cns.stimulate(
|
|
216
|
-
userCreated.createSignal({ id: '123', name: 'John' })
|
|
217
|
-
);
|
|
218
|
-
await stimulation.waitUntilComplete();
|
|
219
|
-
|
|
220
|
-
// Multiple signals at once
|
|
221
|
-
const stimulation = cns.stimulate([
|
|
222
|
-
userCreated.createSignal({ id: '123', name: 'John' }),
|
|
223
|
-
userCreated.createSignal({ id: '456', name: 'Jane' })
|
|
224
|
-
]);
|
|
225
|
-
await stimulation.waitUntilComplete();
|
|
226
|
-
```
|
|
227
|
-
|
|
228
|
-
## ⚙️ Stimulation Options
|
|
229
|
-
|
|
230
|
-
### `maxNeuronHops?: number` (default: 1000)
|
|
231
|
-
Prevents infinite loops by limiting signal traversal depth.
|
|
232
|
-
|
|
233
|
-
```typescript
|
|
234
|
-
const stimulation = cns.stimulate(signal, {
|
|
235
|
-
maxNeuronHops: 50 // Stop after 50 neuron hops
|
|
236
|
-
});
|
|
237
|
-
await stimulation.waitUntilComplete();
|
|
238
|
-
```
|
|
239
|
-
|
|
240
|
-
### `onResponse?: (response) => void`
|
|
241
|
-
Real-time callback for monitoring signal flow and completion.
|
|
242
|
-
|
|
243
|
-
```typescript
|
|
244
|
-
const stimulation = cns.stimulate(signal, {
|
|
245
|
-
onResponse: (response) => {
|
|
246
|
-
console.log(`Signal: ${response.outputSignal?.collateral.id}`);
|
|
247
|
-
console.log(`Hops: ${response.hops}`);
|
|
248
|
-
|
|
249
|
-
if (response.error) {
|
|
250
|
-
console.error('Processing failed:', response.error);
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
if (response.queueLength === 0) {
|
|
254
|
-
console.log('Stimulation completed');
|
|
255
|
-
}
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
await stimulation.waitUntilComplete();
|
|
259
|
-
```
|
|
260
|
-
|
|
261
|
-
**Response Object:**
|
|
262
|
-
- `inputSignal` — The input signal being ingested (if any)
|
|
263
|
-
- `outputSignal` — The output signal being processed (if any)
|
|
264
|
-
- `contextValue` — The context value for this stimulation
|
|
265
|
-
- `queueLength` — Remaining signals in processing queue (0 = complete)
|
|
266
|
-
- `stimulation` — Reference to the CNSStimulation instance
|
|
267
|
-
- `error` — Any error that occurred during processing
|
|
268
|
-
- `hops` — Number of neuron hops taken so far (only present if maxNeuronHops is set)
|
|
269
|
-
- `stimulationId` — Unique identifier for this stimulation
|
|
270
|
-
|
|
271
|
-
### `abortSignal?: AbortSignal`
|
|
272
|
-
Gracefully stop stimulation using AbortController.
|
|
273
|
-
|
|
274
|
-
```typescript
|
|
275
|
-
const controller = new AbortController();
|
|
276
|
-
|
|
277
|
-
const stimulation = cns.stimulate(signal, {
|
|
278
|
-
abortSignal: controller.signal
|
|
279
|
-
});
|
|
280
|
-
|
|
281
|
-
// Cancel after 5 seconds
|
|
282
|
-
setTimeout(() => controller.abort(), 5000);
|
|
283
|
-
await stimulation.waitUntilComplete();
|
|
284
|
-
```
|
|
285
|
-
Inside dendrites, read it via `ctx.abortSignal`.
|
|
286
|
-
|
|
287
|
-
### `stimulationId?: string`
|
|
288
|
-
Custom identifier for this stimulation cascade. Auto-generated if not provided.
|
|
289
|
-
|
|
290
|
-
```typescript
|
|
291
|
-
const stimulation = cns.stimulate(signal, {
|
|
292
|
-
stimulationId: 'user-action-' + Date.now()
|
|
293
|
-
});
|
|
294
|
-
await stimulation.waitUntilComplete();
|
|
295
|
-
```
|
|
296
|
-
|
|
297
|
-
### `allowName?: (collateralName: string) => boolean`
|
|
298
|
-
Filter which collateral names can be processed.
|
|
299
|
-
|
|
300
|
-
```typescript
|
|
301
|
-
const stimulation = cns.stimulate(signal, {
|
|
302
|
-
allowName: (name) => name.startsWith('user:') // Only process user-related signals
|
|
303
|
-
});
|
|
304
|
-
await stimulation.waitUntilComplete();
|
|
305
|
-
```
|
|
306
|
-
|
|
307
|
-
### `concurrency?: number` (default: unlimited)
|
|
308
|
-
Limit concurrent operations to prevent resource exhaustion.
|
|
309
|
-
|
|
310
|
-
```typescript
|
|
311
|
-
const stimulation = cns.stimulate(signal, {
|
|
312
|
-
concurrency: 10 // Max 10 operations at once
|
|
313
|
-
});
|
|
314
|
-
await stimulation.waitUntilComplete();
|
|
315
|
-
```
|
|
316
|
-
|
|
317
|
-
#### Per‑neuron global concurrency
|
|
318
|
-
Set a limit per neuron; parallel stimulations share the same gate.
|
|
319
|
-
```typescript
|
|
320
|
-
const worker = neuron('worker', { out })
|
|
321
|
-
.setConcurrency(2)
|
|
322
|
-
.dendrite({ /* ... */ });
|
|
323
|
-
```
|
|
324
|
-
|
|
325
|
-
### `ctx?: ICNSStimulationContextStore`
|
|
326
|
-
Provide existing context store for recovery/retry scenarios.
|
|
327
|
-
|
|
328
|
-
```typescript
|
|
329
|
-
const stimulation = cns.stimulate(signal, {
|
|
330
|
-
ctx: savedContextStore // Restore previous state
|
|
331
|
-
});
|
|
332
|
-
await stimulation.waitUntilComplete();
|
|
333
|
-
```
|
|
334
|
-
|
|
335
|
-
### `createContextStore?: () => ICNSStimulationContextStore`
|
|
336
|
-
Factory for custom context store implementations.
|
|
337
|
-
|
|
338
|
-
```typescript
|
|
339
|
-
const stimulation = cns.stimulate(signal, {
|
|
340
|
-
createContextStore: () => new CustomContextStore()
|
|
341
|
-
});
|
|
342
|
-
await stimulation.waitUntilComplete();
|
|
343
|
-
```
|
|
344
|
-
|
|
345
|
-
## 🔄 Signal Flow Patterns
|
|
346
|
-
|
|
347
|
-
### Basic Chain Processing
|
|
348
|
-
|
|
349
|
-
```typescript
|
|
350
|
-
const input = collateral<{ value: number }>('input');
|
|
351
|
-
const middle = collateral<{ doubled: number }>('middle');
|
|
352
|
-
const output = collateral<{ result: string }>('output');
|
|
353
|
-
|
|
354
|
-
const step1 = neuron('step1', { middle }).dendrite({
|
|
355
|
-
collateral: input,
|
|
356
|
-
response: (payload, axon) => {
|
|
357
|
-
return axon.middle.createSignal({ doubled: payload.value * 2 });
|
|
358
|
-
}
|
|
359
|
-
});
|
|
360
|
-
|
|
361
|
-
const step2 = neuron('step2', { output }).dendrite({
|
|
362
|
-
collateral: middle,
|
|
363
|
-
response: (payload, axon) => {
|
|
364
|
-
return axon.output.createSignal({ result: `Final: ${payload.doubled}` });
|
|
365
|
-
}
|
|
366
|
-
});
|
|
367
|
-
|
|
368
|
-
const cns = new CNS([step1, step2]);
|
|
369
|
-
|
|
370
|
-
const stimulation = cns.stimulate(input.createSignal({ value: 5 }));
|
|
371
|
-
await stimulation.waitUntilComplete();
|
|
372
|
-
// Flows: input(5) → middle(10) → output("Final: 10")
|
|
373
|
-
```
|
|
374
|
-
|
|
375
|
-
### Context-Aware Processing
|
|
376
|
-
|
|
377
|
-
```typescript
|
|
378
|
-
import { withCtx } from '@cnstra/core';
|
|
379
|
-
|
|
380
|
-
const input = collateral<{ increment: number }>('input');
|
|
381
|
-
const output = collateral<{ count: number }>('output');
|
|
382
|
-
|
|
383
|
-
const counter = withCtx<{ total: number }>()
|
|
384
|
-
.neuron('counter', { output })
|
|
385
|
-
.dendrite({
|
|
386
|
-
collateral: input,
|
|
387
|
-
response: async (payload, axon, ctx) => {
|
|
388
|
-
const current = ctx.get()?.total || 0;
|
|
389
|
-
const newTotal = current + payload.increment;
|
|
390
|
-
|
|
391
|
-
ctx.set({ total: newTotal });
|
|
392
|
-
|
|
393
|
-
return axon.output.createSignal({ count: newTotal });
|
|
394
|
-
}
|
|
395
|
-
});
|
|
396
|
-
|
|
397
|
-
const cns = new CNS([counter]);
|
|
398
|
-
|
|
399
|
-
const stimulation1 = cns.stimulate(input.createSignal({ increment: 5 }));
|
|
400
|
-
await stimulation1.waitUntilComplete(); // count: 5
|
|
401
|
-
const stimulation2 = cns.stimulate(input.createSignal({ increment: 3 }));
|
|
402
|
-
await stimulation2.waitUntilComplete(); // count: 8 (separate context)
|
|
403
|
-
```
|
|
404
|
-
|
|
405
|
-
### Multiple Signals (Fan-out Pattern)
|
|
406
|
-
|
|
407
|
-
Neurons can return arrays of signals for parallel processing:
|
|
408
|
-
|
|
409
|
-
```typescript
|
|
410
|
-
const orderPlaced = collateral<{ orderId: string; items: string[] }>('order:placed');
|
|
411
|
-
const updateInventory = collateral<{ orderId: string; item: string }>('update:inventory');
|
|
412
|
-
const sendEmail = collateral<{ orderId: string }>('send:email');
|
|
413
|
-
const logAudit = collateral<{ orderId: string }>('log:audit');
|
|
414
|
-
|
|
415
|
-
const orderProcessor = neuron('order-processor', {
|
|
416
|
-
updateInventory,
|
|
417
|
-
sendEmail,
|
|
418
|
-
logAudit
|
|
419
|
-
}).dendrite({
|
|
420
|
-
collateral: orderPlaced,
|
|
421
|
-
response: (payload, axon) => {
|
|
422
|
-
// Return array of signals - each will be processed independently
|
|
423
|
-
return [
|
|
424
|
-
// Create signal for each item
|
|
425
|
-
...payload.items.map(item =>
|
|
426
|
-
axon.updateInventory.createSignal({ orderId: payload.orderId, item })
|
|
427
|
-
),
|
|
428
|
-
// Also send confirmation email
|
|
429
|
-
axon.sendEmail.createSignal({ orderId: payload.orderId }),
|
|
430
|
-
// And log the action
|
|
431
|
-
axon.logAudit.createSignal({ orderId: payload.orderId })
|
|
432
|
-
];
|
|
433
|
-
}
|
|
434
|
-
});
|
|
435
|
-
|
|
436
|
-
// Downstream neurons process each signal independently
|
|
437
|
-
const inventoryService = neuron('inventory-service', {}).dendrite({
|
|
438
|
-
collateral: updateInventory,
|
|
439
|
-
response: (payload) => {
|
|
440
|
-
console.log(`Updating inventory for ${payload.item}`);
|
|
441
|
-
// Each item gets its own execution
|
|
442
|
-
}
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
const cns = new CNS([orderProcessor, inventoryService /* ... */]);
|
|
446
|
-
|
|
447
|
-
// Single order triggers multiple parallel operations
|
|
448
|
-
const stimulation = cns.stimulate(
|
|
449
|
-
orderPlaced.createSignal({ orderId: 'ORD-001', items: ['A', 'B', 'C'] })
|
|
450
|
-
);
|
|
451
|
-
await stimulation.waitUntilComplete();
|
|
452
|
-
// Flows: orderPlaced → [inventory(A), inventory(B), inventory(C), email, audit]
|
|
453
|
-
```
|
|
454
|
-
|
|
455
|
-
**Conditional Arrays:**
|
|
456
|
-
```typescript
|
|
457
|
-
const validator = neuron('validator', { success, error, audit }).dendrite({
|
|
458
|
-
collateral: input,
|
|
459
|
-
response: (payload, axon) => {
|
|
460
|
-
const signals = [];
|
|
461
|
-
|
|
462
|
-
// Conditionally add signals
|
|
463
|
-
if (isValid(payload.data)) {
|
|
464
|
-
signals.push(axon.success.createSignal({ validated: payload.data }));
|
|
465
|
-
} else {
|
|
466
|
-
signals.push(axon.error.createSignal({ error: 'Invalid' }));
|
|
467
|
-
}
|
|
468
|
-
|
|
469
|
-
// Always audit
|
|
470
|
-
signals.push(axon.audit.createSignal({ attempted: true }));
|
|
471
|
-
|
|
472
|
-
return signals; // Return empty array to emit nothing
|
|
473
|
-
}
|
|
474
|
-
});
|
|
475
|
-
```
|
|
476
|
-
|
|
477
|
-
**Starting with Multiple Signals:**
|
|
478
|
-
```typescript
|
|
479
|
-
// Process multiple orders in parallel
|
|
480
|
-
const stimulation = cns.stimulate([
|
|
481
|
-
orderPlaced.createSignal({ orderId: 'ORD-001', items: ['X'] }),
|
|
482
|
-
orderPlaced.createSignal({ orderId: 'ORD-002', items: ['Y', 'Z'] })
|
|
483
|
-
]);
|
|
484
|
-
await stimulation.waitUntilComplete();
|
|
485
|
-
```
|
|
486
|
-
|
|
487
|
-
See [Multiple Signals Recipe](https://cnstra.org/docs/recipes/multiple-signals) for more patterns and best practices.
|
|
488
|
-
|
|
489
|
-
## 🧠 Memory & Performance
|
|
490
|
-
|
|
491
|
-
### Memory-Efficient Design
|
|
492
|
-
- **Zero dependencies**: No third-party packages
|
|
493
|
-
- **No error storage**: Errors delivered via callbacks, not stored
|
|
494
|
-
- **Streaming responses**: Signal traces delivered via callbacks
|
|
495
|
-
- **Context on-demand**: Context stores created only when needed
|
|
496
|
-
- **No global state**: Clean slate between stimulations
|
|
497
|
-
|
|
498
|
-
### Performance Characteristics
|
|
499
|
-
- **Sync-first**: Synchronous chains execute in single tick
|
|
500
|
-
- **Minimal async overhead**: Promises created only when needed
|
|
501
|
-
- **Stack-safe**: Handles deep chains without stack overflow
|
|
502
|
-
- **Bounded execution**: `maxNeuronHops` prevents runaway processing
|
|
503
|
-
|
|
504
|
-
### Best Practices
|
|
505
|
-
- Keep context data minimal (IDs, counters, flags)
|
|
506
|
-
- Use synchronous responses when possible
|
|
507
|
-
- Set reasonable `maxNeuronHops` limits
|
|
508
|
-
- Implement proper error handling in `onResponse`
|
|
509
|
-
- Use array returns for genuine fan-out patterns, not just convenience
|
|
510
|
-
- Consider concurrency limits when returning many signals
|
|
511
|
-
|
|
512
|
-
## 🎯 Common Use Cases
|
|
513
|
-
|
|
514
|
-
### HTTP Request Processing
|
|
515
|
-
```typescript
|
|
516
|
-
const httpRequest = collateral<{ method: string; url: string }>('http:request');
|
|
517
|
-
const requestValidated = collateral<{ method: string; url: string }>('request:validated');
|
|
518
|
-
const responseReady = collateral<{ status: number; body: any }>('response:ready');
|
|
519
|
-
|
|
520
|
-
const validator = neuron('validator', { requestValidated }).dendrite({
|
|
521
|
-
collateral: httpRequest,
|
|
522
|
-
response: (payload, axon) => {
|
|
523
|
-
if (!payload.url.startsWith('https://')) {
|
|
524
|
-
throw new Error('Only HTTPS URLs allowed');
|
|
525
|
-
}
|
|
526
|
-
return axon.requestValidated.createSignal(payload);
|
|
527
|
-
}
|
|
528
|
-
});
|
|
529
|
-
|
|
530
|
-
const handler = neuron('handler', { responseReady }).dendrite({
|
|
531
|
-
collateral: requestValidated,
|
|
532
|
-
response: async (payload, axon) => {
|
|
533
|
-
const response = await fetch(payload.url, { method: payload.method });
|
|
534
|
-
const body = await response.json();
|
|
535
|
-
return axon.responseReady.createSignal({ status: response.status, body });
|
|
536
|
-
}
|
|
537
|
-
});
|
|
538
|
-
|
|
539
|
-
const cns = new CNS([validator, handler]);
|
|
540
|
-
```
|
|
541
|
-
|
|
542
|
-
### Event Sourcing
|
|
543
|
-
```typescript
|
|
544
|
-
const eventReceived = collateral<{ name: string; data: any }>('event:received');
|
|
545
|
-
const eventStored = collateral<{ eventId: string }>('event:stored');
|
|
546
|
-
const stateUpdated = collateral<{ aggregateId: string }>('state:updated');
|
|
547
|
-
|
|
548
|
-
const eventStore = neuron('event-store', { eventStored }).dendrite({
|
|
549
|
-
collateral: eventReceived,
|
|
550
|
-
response: async (payload, axon) => {
|
|
551
|
-
const eventId = await saveEvent(payload);
|
|
552
|
-
return axon.eventStored.createSignal({ eventId });
|
|
553
|
-
}
|
|
554
|
-
});
|
|
555
|
-
|
|
556
|
-
const stateManager = neuron('state-manager', { stateUpdated }).dendrite({
|
|
557
|
-
collateral: eventStored,
|
|
558
|
-
response: async (payload, axon) => {
|
|
559
|
-
const aggregateId = await updateState(payload.eventId);
|
|
560
|
-
return axon.stateUpdated.createSignal({ aggregateId });
|
|
561
|
-
}
|
|
562
|
-
});
|
|
563
|
-
|
|
564
|
-
const cns = new CNS([eventStore, stateManager]);
|
|
565
|
-
```
|
|
566
|
-
|
|
567
|
-
## 🚨 Error Handling
|
|
568
|
-
|
|
569
|
-
Errors are delivered immediately via `onResponse` callbacks:
|
|
570
|
-
|
|
571
|
-
```typescript
|
|
572
|
-
const stimulation = cns.stimulate(signal, {
|
|
573
|
-
onResponse: (response) => {
|
|
574
|
-
if (response.error) {
|
|
575
|
-
console.error(`Error in neuron processing:`, response.error);
|
|
576
|
-
|
|
577
|
-
// Log error details
|
|
578
|
-
console.error(`Signal: ${response.outputSignal?.collateral.id}`);
|
|
579
|
-
console.error(`Stimulation: ${response.stimulationId}`);
|
|
580
|
-
|
|
581
|
-
// Handle specific error types
|
|
582
|
-
if (response.error instanceof ValidationError) {
|
|
583
|
-
handleValidationError(response.error);
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
});
|
|
588
|
-
await stimulation.waitUntilComplete();
|
|
589
|
-
```
|
|
590
|
-
|
|
591
|
-
**Error Recovery with Context:**
|
|
592
|
-
```typescript
|
|
593
|
-
let savedContext: ICNSStimulationContextStore | undefined;
|
|
594
|
-
|
|
595
|
-
const stimulation = cns.stimulate(signal, {
|
|
596
|
-
onResponse: (response) => {
|
|
597
|
-
if (response.error) {
|
|
598
|
-
// Save context for retry
|
|
599
|
-
savedContext = response.stimulation.getContext();
|
|
600
|
-
}
|
|
601
|
-
}
|
|
602
|
-
});
|
|
603
|
-
await stimulation.waitUntilComplete();
|
|
604
|
-
|
|
605
|
-
// Retry with preserved context
|
|
606
|
-
if (savedContext) {
|
|
607
|
-
const retryStimulation = cns.stimulate(retrySignal, {
|
|
608
|
-
ctx: savedContext
|
|
609
|
-
});
|
|
610
|
-
await retryStimulation.waitUntilComplete();
|
|
611
|
-
}
|
|
612
|
-
```
|
|
613
|
-
|
|
614
|
-
## 🔧 Advanced Configuration
|
|
615
|
-
|
|
616
|
-
### Custom Context Store
|
|
617
|
-
```typescript
|
|
618
|
-
class RedisContextStore implements ICNSStimulationContextStore {
|
|
619
|
-
constructor(private client: RedisClient, private sessionId: string) {}
|
|
620
|
-
|
|
621
|
-
get<T>(): T | undefined {
|
|
622
|
-
// Implement Redis-backed context retrieval
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
set<T>(value: T): void {
|
|
626
|
-
// Implement Redis-backed context storage
|
|
627
|
-
}
|
|
628
|
-
}
|
|
629
|
-
|
|
630
|
-
const stimulation = cns.stimulate(signal, {
|
|
631
|
-
createContextStore: () => new RedisContextStore(redisClient, 'session-123')
|
|
632
|
-
});
|
|
633
|
-
await stimulation.waitUntilComplete();
|
|
634
|
-
```
|
|
635
|
-
|
|
636
|
-
### CNS Configuration
|
|
637
|
-
```typescript
|
|
638
|
-
const cns = new CNS(neurons, {
|
|
639
|
-
autoCleanupContexts: true, // Auto-cleanup unused contexts
|
|
640
|
-
defaultConcurrency: 50 // Default concurrency limit
|
|
641
|
-
});
|
|
642
|
-
```
|
|
643
|
-
|
|
644
|
-
⚠️ **Performance Warning**: `autoCleanupContexts` adds computational overhead due to:
|
|
645
|
-
- **O(V²) initialization cost** - building SCC (Strongly Connected Components) structures
|
|
646
|
-
- **O(1 + A) runtime cost** per cleanup check (where A = number of SCC ancestors)
|
|
647
|
-
- **Memory overhead** for storing SCC graphs and ancestor relationships
|
|
648
|
-
|
|
649
|
-
**Use only when:**
|
|
650
|
-
- Memory leaks are a critical issue
|
|
651
|
-
- You have a small to medium-sized neuron graph (< 1000 neurons)
|
|
652
|
-
- Performance is less critical than memory management
|
|
53
|
+
## Documentation
|
|
653
54
|
|
|
654
|
-
**
|
|
55
|
+
- **[Quick Start Guide](https://cnstra.org/docs/core/quick-start)** — Get up and running in minutes
|
|
56
|
+
- **[API Reference](https://cnstra.org/docs/core/api)** — Complete API documentation
|
|
57
|
+
- **[Concepts](https://cnstra.org/docs/core/concepts)** — Neurons, collaterals, signals, and the CNS model (Central Neural Network of your app)
|
|
58
|
+
- **[Recipes](https://cnstra.org/docs/recipes)** — Common patterns and use cases
|
|
59
|
+
- **[Advanced Topics](https://cnstra.org/docs/advanced)** — Performance, context stores, integrations
|
|
655
60
|
|
|
656
61
|
---
|
|
657
62
|
|
|
658
|
-
*CNStra provides deterministic, type-safe orchestration without the complexity of traditional event systems
|
|
63
|
+
*CNStra provides deterministic, type-safe orchestration without the complexity of traditional event systems.*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CNSStimulation.d.ts","sourceRoot":"","sources":["../src/CNSStimulation.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,2BAA2B,EAAE,MAAM,0CAA0C,CAAC;AACvF,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAC;AACxE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAC;AAExD,OAAO,EAAE,sBAAsB,EAAE,MAAM,0BAA0B,CAAC;AAClE,OAAO,EAAE,UAAU,EAAE,MAAM,oBAAoB,CAAC;AAChD,OAAO,EAAE,wBAAwB,EAAE,MAAM,kCAAkC,CAAC;AAC5E,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAE5B,OAAO,EAAE,+BAA+B,EAAE,MAAM,yCAAyC,CAAC;AAE1F,qBAAa,cAAc,CACvB,eAAe,SAAS,MAAM,EAC9B,WAAW,SAAS,MAAM,EAC1B,OAAO,SAAS,UAAU,CACtB,GAAG,EACH,WAAW,EACX,eAAe,EACf,GAAG,EACH,GAAG,EACH,GAAG,EACH,GAAG,CACN,EACD,SAAS,SAAS,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,CAAC,EAC5D,aAAa,EACb,cAAc;IAiCV,OAAO,CAAC,QAAQ,CAAC,GAAG;aAOJ,OAAO,CAAC,EAAE,sBAAsB,CAC5C,eAAe,EACf,aAAa,EACb,cAAc,EACd,WAAW,CACd;IA3CL,OAAO,CAAC,QAAQ,CAAC,GAAG,CAA8B;IAClD,OAAO,CAAC,QAAQ,CAAC,KAAK,CAGpB;IACF,OAAO,CAAC,QAAQ,CAAC,cAAc,CAAC,CAA2B;IAC3D,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAkC;IACtE,OAAO,CAAC,cAAc,CAAK;IAC3B,OAAO,CAAC,QAAQ,CAAC,YAAY,CAEzB;IACJ,OAAO,CAAC,QAAQ,CAAC,WAAW,CAErB;IACP,OAAO,CAAC,SAAS,CAGd;IACH,OAAO,CAAC,gBAAgB,CAAc;IACtC,OAAO,CAAC,eAAe,CAAoB;IAC3C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,eAAe,CAAoB;IAE3C;;OAEG;IACH,OAAO,CAAC,QAAQ,CAAC,eAAe,CAA6B;IAE7D,SAAgB,EAAE,EAAE,MAAM,CAAC;gBAGN,GAAG,EAAE,GAAG,CACrB,eAAe,EACf,WAAW,EACX,OAAO,EACP,SAAS,CACZ,EACD,mBAAmB,EAAE,sBAAsB,CAAC,OAAO,CAAC,EACpC,OAAO,CAAC,EAAE,sBAAsB,CAC5C,eAAe,EACf,aAAa,EACb,cAAc,EACd,WAAW,CACd,YAAA;
|
|
1
|
+
{"version":3,"file":"CNSStimulation.d.ts","sourceRoot":"","sources":["../src/CNSStimulation.ts"],"names":[],"mappings":"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"}
|
package/dist/index.cjs
CHANGED
|
@@ -164,7 +164,7 @@ var CNSStimulation = class {
|
|
|
164
164
|
* Track active SCCs: SCC index -> count of active neurons in that SCC
|
|
165
165
|
*/
|
|
166
166
|
this.activeSccCounts = /* @__PURE__ */ new Map();
|
|
167
|
-
this.ctx = new CNSStimulationContextStore(
|
|
167
|
+
this.ctx = options?.ctx ?? new CNSStimulationContextStore(
|
|
168
168
|
new Map(Object.entries(options?.contextValues ?? {}))
|
|
169
169
|
);
|
|
170
170
|
this.id = options?.stimulationId || Math.random().toString(36).slice(2);
|