@cnstra/core 1.0.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/README.md ADDED
@@ -0,0 +1,226 @@
1
+ # @cnstra/core
2
+
3
+ **Graph-routed, type-safe orchestration for reactive apps β€” no global event bus.**
4
+
5
+ ## 🧠 What is CNStra?
6
+
7
+ **CNStra (Central Nervous System Orchestrator)** models your app as a **typed neuron graph**.
8
+ 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
+
10
+ > **Not pub/sub:** there are no ambient listeners or global `emit`. Only the **signal you return** from a dendrite continues the traversal; returning `null`/`undefined` ends that branch. `maxHops` guards against cycles.
11
+
12
+ ## πŸ—οΈ Core Model
13
+
14
+ ### Neurons
15
+ Units of logic with clear DI and sharp boundaries:
16
+ - **ID** β€” unique name
17
+ - **Axon** β€” the neuron’s **output channels** (its collaterals)
18
+ - **Dendrites** β€” **input receptors** (typed reactions bound to specific collaterals)
19
+
20
+ ### Collaterals
21
+ Typed **output channels** that mint signals:
22
+ - **ID** β€” string identifier (e.g., `'user:created'`)
23
+ - **Payload** β€” the shape carried by the signal
24
+ - `createSignal(payload)` β†’ `{ type, payload }`
25
+
26
+ > **Afferent axon:** the object of collaterals you expose publicly. Its **keys** (e.g., `userCreated`) are what you pass to `cns.stimulate(...)`, not the string IDs.
27
+
28
+ ## πŸš€ Quick Start
29
+
30
+ ```bash
31
+ npm install @cnstra/core
32
+ ```
33
+
34
+ ```typescript
35
+ import { CNS, collateral, neuron } from '@cnstra/core';
36
+
37
+ // Define collaterals (communication channels)
38
+ const userCreated = collateral<{ id: string; name: string }>('user:created');
39
+ const userRegistered = collateral<{ userId: string; status: string }>('user:registered');
40
+
41
+ // Create a neuron
42
+ const userService = neuron('user-service', {
43
+ userRegistered
44
+ })
45
+ .dendrite({
46
+ collateral: userCreated,
47
+ reaction: async (payload, axon) => {
48
+ const userData = payload as { id: string; name: string };
49
+
50
+ // Process the user creation
51
+ console.log(`Processing user: ${userData.name}`);
52
+
53
+ // Return the signal that will be processed by CNS
54
+ return axon.userRegistered.createSignal({
55
+ userId: userData.id,
56
+ status: 'completed'
57
+ });
58
+ }
59
+ });
60
+
61
+ // Create the CNS system
62
+ const cns = new CNS({ userCreated }, [userService]);
63
+
64
+ // Stimulate the system
65
+ await cns.stimulate('userCreated', {
66
+ id: '123',
67
+ name: 'John Doe'
68
+ });
69
+ ```
70
+
71
+ ## πŸ“š API Reference
72
+
73
+ ### `collateral<T>(id: string)`
74
+
75
+ Creates a new collateral (communication channel).
76
+
77
+ ```typescript
78
+ const userEvent = collateral<{ userId: string }>('user:event');
79
+ const simpleEvent = collateral('simple:event'); // No payload type
80
+ ```
81
+
82
+ ### `neuron(id: string, axon: Axon)`
83
+
84
+ Creates a new neuron with the specified axon (output channels).
85
+
86
+ ```typescript
87
+ const myNeuron = neuron('my-neuron', {
88
+ output: myCollateral
89
+ });
90
+ ```
91
+
92
+ ### `neuron.dendrite(dendrite: Dendrite)`
93
+
94
+ Adds a dendrite (input receptor) to a neuron. Returns the neuron for chaining.
95
+
96
+ ```typescript
97
+ myNeuron
98
+ .dendrite({
99
+ collateral: inputCollateral,
100
+ reaction: async (payload, axon) => {
101
+ // Process input and return output signal
102
+ return axon.output.createSignal(result);
103
+ }
104
+ });
105
+ ```
106
+
107
+ ### `CNS` Class
108
+
109
+ The main orchestrator that manages signal flow between neurons.
110
+
111
+ #### Constructor
112
+ ```typescript
113
+ new CNS(afferentAxon, neurons)
114
+ ```
115
+
116
+ **Parameters:**
117
+ - `afferentAxon`: Object containing collaterals that can be used as input triggers
118
+ - `neurons`: Array of neurons that process signals
119
+
120
+ #### `stimulate()` Method
121
+ ```typescript
122
+ await cns.stimulate(axonKey, payload, options?)
123
+ ```
124
+
125
+ **Parameters:**
126
+ - `axonKey`: Key from the afferent axon to trigger
127
+ - `payload`: Signal payload with type and data
128
+ - `options`: Optional configuration object
129
+
130
+ **Options:**
131
+ - `maxHops`: Maximum number of signal hops (default: 1000)
132
+ - `onTrace`: Callback for tracing signal flow
133
+ - `abortSignal`: AbortController signal for cancellation
134
+
135
+ ## πŸ”„ Key Behavior
136
+
137
+ ### Only Returned Signals Are Processed
138
+ **Important**: In CNS, only the signal returned from the `reaction` function is processed and propagated to other neurons. Signals created with `axon.collateral.createSignal()` but not returned are NOT processed by the system.
139
+
140
+ ```typescript
141
+ const processor = neuron('processor', {
142
+ output: outputCollateral
143
+ })
144
+ .dendrite({
145
+ collateral: inputCollateral,
146
+ reaction: async (payload, axon) => {
147
+ // This signal is created but NOT processed
148
+ axon.output.createSignal({ message: 'Hello' });
149
+
150
+ // Only this returned signal is processed
151
+ return axon.output.createSignal({ message: 'World' });
152
+ }
153
+ });
154
+ ```
155
+
156
+ ### Multiple Neurons on Same Collateral
157
+ Multiple neurons can listen to the same collateral:
158
+
159
+ ```typescript
160
+ const emailService = neuron('email-service', { emailSent })
161
+ .dendrite({
162
+ collateral: userCreated,
163
+ reaction: async (payload, axon) => {
164
+ return axon.emailSent.createSignal({ to: 'user@example.com' });
165
+ }
166
+ });
167
+
168
+ const notificationService = neuron('notification-service', { notificationSent })
169
+ .dendrite({
170
+ collateral: userCreated,
171
+ reaction: async (payload, axon) => {
172
+ return axon.notificationSent.createSignal({ message: 'User created' });
173
+ }
174
+ });
175
+
176
+ // Both neurons will process the userCreated signal
177
+ const cns = new CNS({ userCreated }, [emailService, notificationService]);
178
+ ```
179
+
180
+ ### Conditional Logic
181
+ ```typescript
182
+ const router = neuron('router', {
183
+ success: successCollateral,
184
+ error: errorCollateral
185
+ })
186
+ .dendrite({
187
+ collateral: requestCollateral,
188
+ reaction: async (payload, axon) => {
189
+ try {
190
+ const result = await processRequest(payload);
191
+ return axon.success.createSignal(result);
192
+ } catch (error) {
193
+ return axon.error.createSignal({ error: error.message });
194
+ }
195
+ }
196
+ });
197
+ ```
198
+
199
+ ## πŸ§ͺ Testing
200
+
201
+ ```bash
202
+ npm test
203
+ npm run test:types
204
+ ```
205
+
206
+ ## πŸ“¦ Build
207
+
208
+ ```bash
209
+ npm run build
210
+ ```
211
+
212
+ ## πŸš€ Examples
213
+
214
+ Run the examples to see CNS in action:
215
+
216
+ ```bash
217
+ npm run examples
218
+ ```
219
+
220
+ ## 🀝 Contributing
221
+
222
+ Contributions are welcome! Please feel free to submit a Pull Request.
223
+
224
+ ## πŸ“„ License
225
+
226
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ CNS: () => CNS,
24
+ CNSCollateral: () => CNSCollateral,
25
+ collateral: () => collateral,
26
+ neuron: () => neuron
27
+ });
28
+ module.exports = __toCommonJS(index_exports);
29
+
30
+ // src/CNS.ts
31
+ var asArray = (x) => Array.isArray(x) ? x : [x];
32
+ var CNS = class {
33
+ constructor(afferentLayerAsAxon, neurons) {
34
+ this.afferentLayerAsAxon = afferentLayerAsAxon;
35
+ this.neurons = neurons;
36
+ // index by collateral *type* => list of subscribers
37
+ this.subIndex = /* @__PURE__ */ new Map();
38
+ this.buildIndex();
39
+ }
40
+ buildIndex() {
41
+ this.subIndex.clear();
42
+ for (const neuron2 of this.neurons) {
43
+ for (const dendrite of neuron2.dendrites) {
44
+ const key = dendrite.collateral.id;
45
+ const arr = this.subIndex.get(key) ?? [];
46
+ arr.push({ neuron: neuron2, dendrite });
47
+ this.subIndex.set(
48
+ key,
49
+ arr
50
+ );
51
+ }
52
+ }
53
+ }
54
+ async fanOut(incoming, allowType, seen, queue) {
55
+ const subscribers = this.subIndex.get(incoming.edgeId);
56
+ if (!subscribers || subscribers.length === 0) return;
57
+ for (const { neuron: neuron2, dendrite } of subscribers) {
58
+ const k = `${neuron2.id}::${incoming.edgeId}::${incoming.spikeId}`;
59
+ if (seen.has(k)) continue;
60
+ seen.add(k);
61
+ const out = await dendrite.reaction(incoming.payload, neuron2.axon);
62
+ if (out === void 0) continue;
63
+ for (const spike of asArray(out)) {
64
+ if (allowType && !allowType(spike.type))
65
+ continue;
66
+ queue.push({
67
+ edgeId: spike.type,
68
+ payload: spike.payload,
69
+ hops: incoming.hops + 1,
70
+ spikeId: incoming.spikeId
71
+ // keep the same spike id across the cascade
72
+ });
73
+ }
74
+ }
75
+ }
76
+ async stimulate(axonKey, payload, opts) {
77
+ const afferent = this.afferentLayerAsAxon[axonKey];
78
+ const spikeId = opts?.spikeId || Math.random().toString(36).slice(2);
79
+ const maxHops = opts?.maxHops ?? 1e3;
80
+ const queue = [
81
+ {
82
+ edgeId: afferent.id,
83
+ payload,
84
+ hops: 0,
85
+ spikeId
86
+ }
87
+ ];
88
+ const seen = /* @__PURE__ */ new Set();
89
+ while (queue.length) {
90
+ if (opts?.abortSignal?.aborted) break;
91
+ const item = queue.shift();
92
+ opts?.onTrace?.({
93
+ edgeId: item.edgeId,
94
+ hops: item.hops,
95
+ payload: item.payload
96
+ });
97
+ if (item.hops >= maxHops) continue;
98
+ await this.fanOut(
99
+ item,
100
+ opts?.allowType,
101
+ seen,
102
+ queue
103
+ );
104
+ }
105
+ }
106
+ };
107
+
108
+ // src/CNSCollateral.ts
109
+ var CNSCollateral = class {
110
+ constructor(id) {
111
+ this.id = id;
112
+ }
113
+ createSignal(...args) {
114
+ const payload = args[0];
115
+ return { type: this.id, payload };
116
+ }
117
+ };
118
+
119
+ // src/factory/index.ts
120
+ var collateral = (id) => new CNSCollateral(id);
121
+ var neuron = (id, axon) => {
122
+ const dendrites = [];
123
+ const api = {
124
+ axon,
125
+ id,
126
+ dendrites,
127
+ dendrite(s) {
128
+ dendrites.push(s);
129
+ return api;
130
+ }
131
+ };
132
+ return api;
133
+ };
134
+ // Annotate the CommonJS export names for ESM import in node:
135
+ 0 && (module.exports = {
136
+ CNS,
137
+ CNSCollateral,
138
+ collateral,
139
+ neuron
140
+ });
@@ -0,0 +1,56 @@
1
+ type TCNSSignal<TType extends string, TPayload> = {
2
+ type: TType;
3
+ payload?: TPayload;
4
+ };
5
+
6
+ declare class CNSCollateral<TId extends string, TPayload> {
7
+ readonly id: TId;
8
+ constructor(id: TId);
9
+ createSignal(...args: [TPayload] extends [undefined] ? [] : [payload: TPayload]): TCNSSignal<TId, TPayload>;
10
+ }
11
+
12
+ type TCNSAxon<TCId extends string, TInputPayload = unknown> = Record<TCId, CNSCollateral<TCId, TInputPayload>>;
13
+
14
+ type TCNDendrite<TSenderCollateralIdType extends string, TReceiverAxonCollateralPayload, TReceiverCollateralIdType extends string, TReceiverAxon extends TCNSAxon<TReceiverCollateralIdType, TReceiverAxonCollateralPayload>> = {
15
+ collateral: CNSCollateral<TSenderCollateralIdType, TReceiverAxonCollateralPayload>;
16
+ reaction: (payload: TReceiverAxonCollateralPayload, axon: TReceiverAxon) => Promise<ReturnType<CNSCollateral<TReceiverCollateralIdType, TReceiverAxonCollateralPayload>['createSignal']>> | ReturnType<CNSCollateral<TReceiverCollateralIdType, TReceiverAxonCollateralPayload>['createSignal']> | Promise<void> | void;
17
+ };
18
+
19
+ type TCNSNeuron<TId extends string, TCollateralId extends string, TAxon extends TCNSAxon<TCollateralId, unknown>, TSenderCollateralId extends string, TDendrites extends TCNDendrite<TSenderCollateralId, unknown, TCollateralId, TAxon>[]> = {
20
+ id: TId;
21
+ axon: TAxon;
22
+ dendrites: TDendrites;
23
+ };
24
+
25
+ declare class CNS<TAfferentCollateralId extends string, TAfferentAxon extends TCNSAxon<TAfferentCollateralId, unknown>, TNeuronId extends string, TCollateralId extends string, TAxon extends TCNSAxon<TCollateralId, any>> {
26
+ protected readonly afferentLayerAsAxon: TAfferentAxon;
27
+ protected readonly neurons: TCNSNeuron<TNeuronId, TCollateralId, TAxon, TCollateralId, TCNDendrite<TCollateralId, any, TCollateralId, TAxon>[]>[];
28
+ private subIndex;
29
+ constructor(afferentLayerAsAxon: TAfferentAxon, neurons: TCNSNeuron<TNeuronId, TCollateralId, TAxon, TCollateralId, TCNDendrite<TCollateralId, any, TCollateralId, TAxon>[]>[]);
30
+ private buildIndex;
31
+ private fanOut;
32
+ stimulate<K extends keyof TAfferentAxon, TACollateralId extends string>(axonKey: K, payload: ReturnType<TAfferentAxon[K]['createSignal']> & {
33
+ type: TACollateralId;
34
+ }, opts?: {
35
+ maxHops?: number;
36
+ allowType?: (t: TCollateralId | TAfferentCollateralId) => boolean;
37
+ onTrace?: (e: {
38
+ edgeId: TCollateralId | TAfferentCollateralId;
39
+ hops: number;
40
+ payload: unknown;
41
+ }) => void;
42
+ abortSignal?: AbortSignal;
43
+ spikeId?: string;
44
+ }): Promise<void>;
45
+ }
46
+
47
+ declare const collateral: <TPayload = undefined, TId extends string = string>(id: TId) => CNSCollateral<TId, TPayload>;
48
+ type InterNeuronAPI<TIdType extends string, TSenderCollateralIdType extends string, TReceiverAxonCollateralPayload, TReceiverCollateralIdType extends string, TReceiverAxon extends TCNSAxon<TReceiverCollateralIdType, TReceiverAxonCollateralPayload>> = {
49
+ id: TIdType;
50
+ axon: TReceiverAxon;
51
+ dendrites: TCNDendrite<TSenderCollateralIdType, TReceiverAxonCollateralPayload, TReceiverCollateralIdType, TReceiverAxon>[];
52
+ dendrite: (s: TCNDendrite<TSenderCollateralIdType, TReceiverAxonCollateralPayload, TReceiverCollateralIdType, TReceiverAxon>) => InterNeuronAPI<TIdType, TSenderCollateralIdType, TReceiverAxonCollateralPayload, TReceiverCollateralIdType, TReceiverAxon>;
53
+ };
54
+ declare const neuron: <TIdType extends string, TSenderCollateralIdType extends string, TReceiverAxonCollateralPayload, TReceiverCollateralIdType extends string, TReceiverAxon extends TCNSAxon<TReceiverCollateralIdType, TReceiverAxonCollateralPayload>>(id: TIdType, axon: TReceiverAxon) => InterNeuronAPI<TIdType, TSenderCollateralIdType, TReceiverAxonCollateralPayload, TReceiverCollateralIdType, TReceiverAxon>;
55
+
56
+ export { CNS, CNSCollateral, type TCNDendrite, type TCNSAxon, type TCNSNeuron, type TCNSSignal, collateral, neuron };
@@ -0,0 +1,56 @@
1
+ type TCNSSignal<TType extends string, TPayload> = {
2
+ type: TType;
3
+ payload?: TPayload;
4
+ };
5
+
6
+ declare class CNSCollateral<TId extends string, TPayload> {
7
+ readonly id: TId;
8
+ constructor(id: TId);
9
+ createSignal(...args: [TPayload] extends [undefined] ? [] : [payload: TPayload]): TCNSSignal<TId, TPayload>;
10
+ }
11
+
12
+ type TCNSAxon<TCId extends string, TInputPayload = unknown> = Record<TCId, CNSCollateral<TCId, TInputPayload>>;
13
+
14
+ type TCNDendrite<TSenderCollateralIdType extends string, TReceiverAxonCollateralPayload, TReceiverCollateralIdType extends string, TReceiverAxon extends TCNSAxon<TReceiverCollateralIdType, TReceiverAxonCollateralPayload>> = {
15
+ collateral: CNSCollateral<TSenderCollateralIdType, TReceiverAxonCollateralPayload>;
16
+ reaction: (payload: TReceiverAxonCollateralPayload, axon: TReceiverAxon) => Promise<ReturnType<CNSCollateral<TReceiverCollateralIdType, TReceiverAxonCollateralPayload>['createSignal']>> | ReturnType<CNSCollateral<TReceiverCollateralIdType, TReceiverAxonCollateralPayload>['createSignal']> | Promise<void> | void;
17
+ };
18
+
19
+ type TCNSNeuron<TId extends string, TCollateralId extends string, TAxon extends TCNSAxon<TCollateralId, unknown>, TSenderCollateralId extends string, TDendrites extends TCNDendrite<TSenderCollateralId, unknown, TCollateralId, TAxon>[]> = {
20
+ id: TId;
21
+ axon: TAxon;
22
+ dendrites: TDendrites;
23
+ };
24
+
25
+ declare class CNS<TAfferentCollateralId extends string, TAfferentAxon extends TCNSAxon<TAfferentCollateralId, unknown>, TNeuronId extends string, TCollateralId extends string, TAxon extends TCNSAxon<TCollateralId, any>> {
26
+ protected readonly afferentLayerAsAxon: TAfferentAxon;
27
+ protected readonly neurons: TCNSNeuron<TNeuronId, TCollateralId, TAxon, TCollateralId, TCNDendrite<TCollateralId, any, TCollateralId, TAxon>[]>[];
28
+ private subIndex;
29
+ constructor(afferentLayerAsAxon: TAfferentAxon, neurons: TCNSNeuron<TNeuronId, TCollateralId, TAxon, TCollateralId, TCNDendrite<TCollateralId, any, TCollateralId, TAxon>[]>[]);
30
+ private buildIndex;
31
+ private fanOut;
32
+ stimulate<K extends keyof TAfferentAxon, TACollateralId extends string>(axonKey: K, payload: ReturnType<TAfferentAxon[K]['createSignal']> & {
33
+ type: TACollateralId;
34
+ }, opts?: {
35
+ maxHops?: number;
36
+ allowType?: (t: TCollateralId | TAfferentCollateralId) => boolean;
37
+ onTrace?: (e: {
38
+ edgeId: TCollateralId | TAfferentCollateralId;
39
+ hops: number;
40
+ payload: unknown;
41
+ }) => void;
42
+ abortSignal?: AbortSignal;
43
+ spikeId?: string;
44
+ }): Promise<void>;
45
+ }
46
+
47
+ declare const collateral: <TPayload = undefined, TId extends string = string>(id: TId) => CNSCollateral<TId, TPayload>;
48
+ type InterNeuronAPI<TIdType extends string, TSenderCollateralIdType extends string, TReceiverAxonCollateralPayload, TReceiverCollateralIdType extends string, TReceiverAxon extends TCNSAxon<TReceiverCollateralIdType, TReceiverAxonCollateralPayload>> = {
49
+ id: TIdType;
50
+ axon: TReceiverAxon;
51
+ dendrites: TCNDendrite<TSenderCollateralIdType, TReceiverAxonCollateralPayload, TReceiverCollateralIdType, TReceiverAxon>[];
52
+ dendrite: (s: TCNDendrite<TSenderCollateralIdType, TReceiverAxonCollateralPayload, TReceiverCollateralIdType, TReceiverAxon>) => InterNeuronAPI<TIdType, TSenderCollateralIdType, TReceiverAxonCollateralPayload, TReceiverCollateralIdType, TReceiverAxon>;
53
+ };
54
+ declare const neuron: <TIdType extends string, TSenderCollateralIdType extends string, TReceiverAxonCollateralPayload, TReceiverCollateralIdType extends string, TReceiverAxon extends TCNSAxon<TReceiverCollateralIdType, TReceiverAxonCollateralPayload>>(id: TIdType, axon: TReceiverAxon) => InterNeuronAPI<TIdType, TSenderCollateralIdType, TReceiverAxonCollateralPayload, TReceiverCollateralIdType, TReceiverAxon>;
55
+
56
+ export { CNS, CNSCollateral, type TCNDendrite, type TCNSAxon, type TCNSNeuron, type TCNSSignal, collateral, neuron };
package/dist/index.js ADDED
@@ -0,0 +1,110 @@
1
+ // src/CNS.ts
2
+ var asArray = (x) => Array.isArray(x) ? x : [x];
3
+ var CNS = class {
4
+ constructor(afferentLayerAsAxon, neurons) {
5
+ this.afferentLayerAsAxon = afferentLayerAsAxon;
6
+ this.neurons = neurons;
7
+ // index by collateral *type* => list of subscribers
8
+ this.subIndex = /* @__PURE__ */ new Map();
9
+ this.buildIndex();
10
+ }
11
+ buildIndex() {
12
+ this.subIndex.clear();
13
+ for (const neuron2 of this.neurons) {
14
+ for (const dendrite of neuron2.dendrites) {
15
+ const key = dendrite.collateral.id;
16
+ const arr = this.subIndex.get(key) ?? [];
17
+ arr.push({ neuron: neuron2, dendrite });
18
+ this.subIndex.set(
19
+ key,
20
+ arr
21
+ );
22
+ }
23
+ }
24
+ }
25
+ async fanOut(incoming, allowType, seen, queue) {
26
+ const subscribers = this.subIndex.get(incoming.edgeId);
27
+ if (!subscribers || subscribers.length === 0) return;
28
+ for (const { neuron: neuron2, dendrite } of subscribers) {
29
+ const k = `${neuron2.id}::${incoming.edgeId}::${incoming.spikeId}`;
30
+ if (seen.has(k)) continue;
31
+ seen.add(k);
32
+ const out = await dendrite.reaction(incoming.payload, neuron2.axon);
33
+ if (out === void 0) continue;
34
+ for (const spike of asArray(out)) {
35
+ if (allowType && !allowType(spike.type))
36
+ continue;
37
+ queue.push({
38
+ edgeId: spike.type,
39
+ payload: spike.payload,
40
+ hops: incoming.hops + 1,
41
+ spikeId: incoming.spikeId
42
+ // keep the same spike id across the cascade
43
+ });
44
+ }
45
+ }
46
+ }
47
+ async stimulate(axonKey, payload, opts) {
48
+ const afferent = this.afferentLayerAsAxon[axonKey];
49
+ const spikeId = opts?.spikeId || Math.random().toString(36).slice(2);
50
+ const maxHops = opts?.maxHops ?? 1e3;
51
+ const queue = [
52
+ {
53
+ edgeId: afferent.id,
54
+ payload,
55
+ hops: 0,
56
+ spikeId
57
+ }
58
+ ];
59
+ const seen = /* @__PURE__ */ new Set();
60
+ while (queue.length) {
61
+ if (opts?.abortSignal?.aborted) break;
62
+ const item = queue.shift();
63
+ opts?.onTrace?.({
64
+ edgeId: item.edgeId,
65
+ hops: item.hops,
66
+ payload: item.payload
67
+ });
68
+ if (item.hops >= maxHops) continue;
69
+ await this.fanOut(
70
+ item,
71
+ opts?.allowType,
72
+ seen,
73
+ queue
74
+ );
75
+ }
76
+ }
77
+ };
78
+
79
+ // src/CNSCollateral.ts
80
+ var CNSCollateral = class {
81
+ constructor(id) {
82
+ this.id = id;
83
+ }
84
+ createSignal(...args) {
85
+ const payload = args[0];
86
+ return { type: this.id, payload };
87
+ }
88
+ };
89
+
90
+ // src/factory/index.ts
91
+ var collateral = (id) => new CNSCollateral(id);
92
+ var neuron = (id, axon) => {
93
+ const dendrites = [];
94
+ const api = {
95
+ axon,
96
+ id,
97
+ dendrites,
98
+ dendrite(s) {
99
+ dendrites.push(s);
100
+ return api;
101
+ }
102
+ };
103
+ return api;
104
+ };
105
+ export {
106
+ CNS,
107
+ CNSCollateral,
108
+ collateral,
109
+ neuron
110
+ };
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "@cnstra/core",
3
+ "version": "1.0.0",
4
+ "description": "Neural network-inspired event flow system for building reactive applications with dependency-injected neurons and synapses",
5
+ "keywords": [
6
+ "neural-network",
7
+ "event-flow",
8
+ "reactive",
9
+ "dependency-injection",
10
+ "neuron",
11
+ "synapse",
12
+ "axon",
13
+ "dendrite",
14
+ "typescript",
15
+ "node",
16
+ "browser"
17
+ ],
18
+ "type": "module",
19
+ "main": "./dist/index.cjs",
20
+ "module": "./dist/index.js",
21
+ "types": "./dist/index.d.ts",
22
+ "sideEffects": false,
23
+ "repository": {
24
+ "type": "git",
25
+ "url": "git+https://github.com/abaikov/cnstra.git"
26
+ },
27
+ "bugs": {
28
+ "url": "https://github.com/abaikov/cnstra/issues"
29
+ },
30
+ "homepage": "https://github.com/abaikov/cnstra#readme",
31
+ "exports": {
32
+ ".": {
33
+ "types": "./dist/index.d.ts",
34
+ "import": "./dist/index.js",
35
+ "require": "./dist/index.cjs"
36
+ }
37
+ },
38
+ "scripts": {
39
+ "build": "tsup src/index.ts --dts --format esm,cjs --tsconfig tsconfig.build.json",
40
+ "prepublishOnly": "npm run build",
41
+ "test": "jest",
42
+ "test:types": "tsd",
43
+ "examples": "tsx examples/run-examples.ts"
44
+ },
45
+ "files": [
46
+ "dist"
47
+ ],
48
+ "author": "Andrei Baikov <abaikov23@gmail.com>",
49
+ "license": "MIT",
50
+ "devDependencies": {
51
+ "@types/jest": "^29.5.12",
52
+ "jest": "^29.7.0",
53
+ "ts-jest": "^29.1.1",
54
+ "tsup": "^8.5.0",
55
+ "tsx": "^4.20.5",
56
+ "typescript": "^5.8.3"
57
+ }
58
+ }