@timmeck/brain-core 1.1.0 → 1.5.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 +66 -14
- package/brain.log +6 -0
- package/dist/config/__tests__/loader.test.d.ts +1 -0
- package/dist/config/__tests__/loader.test.js +85 -0
- package/dist/config/__tests__/loader.test.js.map +1 -0
- package/dist/config/loader.d.ts +15 -0
- package/dist/config/loader.js +39 -0
- package/dist/config/loader.js.map +1 -0
- package/dist/cross-brain/__tests__/client.test.d.ts +1 -0
- package/dist/cross-brain/__tests__/client.test.js +31 -0
- package/dist/cross-brain/__tests__/client.test.js.map +1 -0
- package/dist/cross-brain/__tests__/notifications.test.d.ts +1 -0
- package/dist/cross-brain/__tests__/notifications.test.js +52 -0
- package/dist/cross-brain/__tests__/notifications.test.js.map +1 -0
- package/dist/cross-brain/notifications.d.ts +25 -0
- package/dist/cross-brain/notifications.js +51 -0
- package/dist/cross-brain/notifications.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.js +14 -0
- package/dist/index.js.map +1 -1
- package/dist/ipc/__tests__/protocol.test.d.ts +1 -0
- package/dist/ipc/__tests__/protocol.test.js +69 -0
- package/dist/ipc/__tests__/protocol.test.js.map +1 -0
- package/dist/learning/__tests__/base-engine.test.d.ts +1 -0
- package/dist/learning/__tests__/base-engine.test.js +49 -0
- package/dist/learning/__tests__/base-engine.test.js.map +1 -0
- package/dist/learning/base-engine.d.ts +16 -0
- package/dist/learning/base-engine.js +30 -0
- package/dist/learning/base-engine.js.map +1 -0
- package/dist/math/__tests__/time-decay.test.d.ts +1 -0
- package/dist/math/__tests__/time-decay.test.js +37 -0
- package/dist/math/__tests__/time-decay.test.js.map +1 -0
- package/dist/math/__tests__/wilson-score.test.d.ts +1 -0
- package/dist/math/__tests__/wilson-score.test.js +43 -0
- package/dist/math/__tests__/wilson-score.test.js.map +1 -0
- package/dist/math/time-decay.d.ts +10 -0
- package/dist/math/time-decay.js +16 -0
- package/dist/math/time-decay.js.map +1 -0
- package/dist/math/wilson-score.d.ts +10 -0
- package/dist/math/wilson-score.js +21 -0
- package/dist/math/wilson-score.js.map +1 -0
- package/dist/research/__tests__/base-engine.test.d.ts +1 -0
- package/dist/research/__tests__/base-engine.test.js +56 -0
- package/dist/research/__tests__/base-engine.test.js.map +1 -0
- package/dist/research/base-engine.d.ts +20 -0
- package/dist/research/base-engine.js +46 -0
- package/dist/research/base-engine.js.map +1 -0
- package/dist/synapses/__tests__/activation.test.d.ts +1 -0
- package/dist/synapses/__tests__/activation.test.js +87 -0
- package/dist/synapses/__tests__/activation.test.js.map +1 -0
- package/dist/synapses/__tests__/decay.test.d.ts +1 -0
- package/dist/synapses/__tests__/decay.test.js +73 -0
- package/dist/synapses/__tests__/decay.test.js.map +1 -0
- package/dist/synapses/__tests__/hebbian.test.d.ts +1 -0
- package/dist/synapses/__tests__/hebbian.test.js +95 -0
- package/dist/synapses/__tests__/hebbian.test.js.map +1 -0
- package/dist/synapses/__tests__/pathfinder.test.d.ts +1 -0
- package/dist/synapses/__tests__/pathfinder.test.js +74 -0
- package/dist/synapses/__tests__/pathfinder.test.js.map +1 -0
- package/dist/synapses/__tests__/synapse-manager.test.d.ts +1 -0
- package/dist/synapses/__tests__/synapse-manager.test.js +94 -0
- package/dist/synapses/__tests__/synapse-manager.test.js.map +1 -0
- package/dist/synapses/activation.d.ts +6 -0
- package/dist/synapses/activation.js +54 -0
- package/dist/synapses/activation.js.map +1 -0
- package/dist/synapses/decay.d.ts +9 -0
- package/dist/synapses/decay.js +26 -0
- package/dist/synapses/decay.js.map +1 -0
- package/dist/synapses/hebbian.d.ts +12 -0
- package/dist/synapses/hebbian.js +45 -0
- package/dist/synapses/hebbian.js.map +1 -0
- package/dist/synapses/pathfinder.d.ts +6 -0
- package/dist/synapses/pathfinder.js +54 -0
- package/dist/synapses/pathfinder.js.map +1 -0
- package/dist/synapses/synapse-manager.d.ts +35 -0
- package/dist/synapses/synapse-manager.js +72 -0
- package/dist/synapses/synapse-manager.js.map +1 -0
- package/dist/synapses/types.d.ts +85 -0
- package/dist/synapses/types.js +7 -0
- package/dist/synapses/types.js.map +1 -0
- package/dist/utils/__tests__/events.test.d.ts +1 -0
- package/dist/utils/__tests__/events.test.js +38 -0
- package/dist/utils/__tests__/events.test.js.map +1 -0
- package/dist/utils/__tests__/hash.test.d.ts +1 -0
- package/dist/utils/__tests__/hash.test.js +25 -0
- package/dist/utils/__tests__/hash.test.js.map +1 -0
- package/dist/utils/__tests__/logger.test.d.ts +1 -0
- package/dist/utils/__tests__/logger.test.js +29 -0
- package/dist/utils/__tests__/logger.test.js.map +1 -0
- package/dist/utils/__tests__/paths.test.d.ts +1 -0
- package/dist/utils/__tests__/paths.test.js +50 -0
- package/dist/utils/__tests__/paths.test.js.map +1 -0
- package/eslint.config.js +14 -0
- package/package.json +17 -2
- package/.github/FUNDING.yml +0 -1
- package/.github/workflows/ci.yml +0 -21
- package/src/api/server.ts +0 -210
- package/src/cli/colors.ts +0 -105
- package/src/cross-brain/client.ts +0 -94
- package/src/db/connection.ts +0 -22
- package/src/index.ts +0 -35
- package/src/ipc/client.ts +0 -117
- package/src/ipc/protocol.ts +0 -35
- package/src/ipc/server.ts +0 -170
- package/src/mcp/http-server.ts +0 -148
- package/src/mcp/server.ts +0 -84
- package/src/types/ipc.types.ts +0 -8
- package/src/utils/events.ts +0 -30
- package/src/utils/hash.ts +0 -5
- package/src/utils/logger.ts +0 -67
- package/src/utils/paths.ts +0 -24
- package/tsconfig.json +0 -18
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { strengthen, weaken } from './hebbian.js';
|
|
2
|
+
import { decayAll } from './decay.js';
|
|
3
|
+
import { spreadingActivation } from './activation.js';
|
|
4
|
+
import { findPath } from './pathfinder.js';
|
|
5
|
+
import { getLogger } from '../utils/logger.js';
|
|
6
|
+
/**
|
|
7
|
+
* Base synapse manager shared across all brains.
|
|
8
|
+
* Each brain extends this with domain-specific context methods
|
|
9
|
+
* (e.g. getErrorContext, getPostContext, getTradeContext).
|
|
10
|
+
*/
|
|
11
|
+
export class BaseSynapseManager {
|
|
12
|
+
repo;
|
|
13
|
+
config;
|
|
14
|
+
logger = getLogger();
|
|
15
|
+
constructor(repo, config) {
|
|
16
|
+
this.repo = repo;
|
|
17
|
+
this.config = config;
|
|
18
|
+
}
|
|
19
|
+
strengthen(source, target, synapseType, context) {
|
|
20
|
+
this.logger.debug(`Strengthening synapse ${source.type}:${source.id} --${synapseType}--> ${target.type}:${target.id}`);
|
|
21
|
+
return strengthen(this.repo, source, target, synapseType, this.hebbianConfig(), context);
|
|
22
|
+
}
|
|
23
|
+
weaken(synapseId, factor = 0.5) {
|
|
24
|
+
this.logger.debug(`Weakening synapse ${synapseId} by factor ${factor}`);
|
|
25
|
+
weaken(this.repo, synapseId, this.hebbianConfig(), factor);
|
|
26
|
+
}
|
|
27
|
+
find(source, target, synapseType) {
|
|
28
|
+
return this.repo.findBySourceTarget(source.type, source.id, target.type, target.id, synapseType);
|
|
29
|
+
}
|
|
30
|
+
activate(startNode, maxDepth, minWeight) {
|
|
31
|
+
return spreadingActivation(this.repo, startNode, maxDepth ?? this.config.maxDepth, minWeight ?? this.config.minActivationWeight);
|
|
32
|
+
}
|
|
33
|
+
findPath(from, to, maxDepth) {
|
|
34
|
+
return findPath(this.repo, from, to, maxDepth ?? this.config.maxDepth + 2);
|
|
35
|
+
}
|
|
36
|
+
runDecay() {
|
|
37
|
+
this.logger.info('Running synapse decay cycle');
|
|
38
|
+
const result = decayAll(this.repo, this.decayConfig());
|
|
39
|
+
this.logger.info(`Decay complete: ${result.decayed} decayed, ${result.pruned} pruned`);
|
|
40
|
+
return result;
|
|
41
|
+
}
|
|
42
|
+
getStrongestSynapses(limit = 20) {
|
|
43
|
+
return this.repo.topByWeight(limit);
|
|
44
|
+
}
|
|
45
|
+
getDiverseSynapses(perType = 25) {
|
|
46
|
+
return this.repo.topDiverse(perType);
|
|
47
|
+
}
|
|
48
|
+
getNetworkStats() {
|
|
49
|
+
return {
|
|
50
|
+
totalNodes: this.repo.countNodes(),
|
|
51
|
+
totalSynapses: this.repo.totalCount(),
|
|
52
|
+
avgWeight: this.repo.avgWeight(),
|
|
53
|
+
nodesByType: {},
|
|
54
|
+
synapsesByType: this.repo.countByType(),
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
hebbianConfig() {
|
|
58
|
+
return {
|
|
59
|
+
initialWeight: this.config.initialWeight,
|
|
60
|
+
learningRate: this.config.learningRate,
|
|
61
|
+
pruneThreshold: this.config.pruneThreshold,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
decayConfig() {
|
|
65
|
+
return {
|
|
66
|
+
decayHalfLifeDays: this.config.decayHalfLifeDays,
|
|
67
|
+
decayAfterDays: this.config.decayAfterDays,
|
|
68
|
+
pruneThreshold: this.config.pruneThreshold,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
//# sourceMappingURL=synapse-manager.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"synapse-manager.js","sourceRoot":"","sources":["../../src/synapses/synapse-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Generic synapse network types for the Brain ecosystem.
|
|
3
|
+
* Each brain provides its own NodeType/SynapseType string unions,
|
|
4
|
+
* but the record shape and algorithms are shared.
|
|
5
|
+
*/
|
|
6
|
+
export interface NodeRef {
|
|
7
|
+
type: string;
|
|
8
|
+
id: number;
|
|
9
|
+
}
|
|
10
|
+
export interface SynapseRecord {
|
|
11
|
+
id: number;
|
|
12
|
+
source_type: string;
|
|
13
|
+
source_id: number;
|
|
14
|
+
target_type: string;
|
|
15
|
+
target_id: number;
|
|
16
|
+
synapse_type: string;
|
|
17
|
+
weight: number;
|
|
18
|
+
activation_count: number;
|
|
19
|
+
last_activated_at: string;
|
|
20
|
+
metadata: string | null;
|
|
21
|
+
created_at: string;
|
|
22
|
+
updated_at: string;
|
|
23
|
+
}
|
|
24
|
+
export interface ActivationResult {
|
|
25
|
+
node: NodeRef;
|
|
26
|
+
activation: number;
|
|
27
|
+
depth: number;
|
|
28
|
+
path: string[];
|
|
29
|
+
}
|
|
30
|
+
export interface PathNode {
|
|
31
|
+
type: string;
|
|
32
|
+
id: number;
|
|
33
|
+
}
|
|
34
|
+
export interface SynapsePath {
|
|
35
|
+
from: PathNode;
|
|
36
|
+
to: PathNode;
|
|
37
|
+
synapses: SynapseRecord[];
|
|
38
|
+
totalWeight: number;
|
|
39
|
+
hops: number;
|
|
40
|
+
}
|
|
41
|
+
export interface NetworkStats {
|
|
42
|
+
totalNodes: number;
|
|
43
|
+
totalSynapses: number;
|
|
44
|
+
avgWeight: number;
|
|
45
|
+
nodesByType: Record<string, number>;
|
|
46
|
+
synapsesByType: Record<string, number>;
|
|
47
|
+
}
|
|
48
|
+
export interface HebbianConfig {
|
|
49
|
+
initialWeight: number;
|
|
50
|
+
learningRate: number;
|
|
51
|
+
pruneThreshold: number;
|
|
52
|
+
}
|
|
53
|
+
export interface DecayConfig {
|
|
54
|
+
decayHalfLifeDays: number;
|
|
55
|
+
decayAfterDays: number;
|
|
56
|
+
pruneThreshold: number;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Interface that synapse repositories must implement
|
|
60
|
+
* for the shared algorithms to work.
|
|
61
|
+
*/
|
|
62
|
+
export interface SynapseRepoInterface {
|
|
63
|
+
findBySourceTarget(sourceType: string, sourceId: number, targetType: string, targetId: number, synapseType: string): SynapseRecord | undefined;
|
|
64
|
+
create(data: {
|
|
65
|
+
source_type: string;
|
|
66
|
+
source_id: number;
|
|
67
|
+
target_type: string;
|
|
68
|
+
target_id: number;
|
|
69
|
+
synapse_type: string;
|
|
70
|
+
weight: number;
|
|
71
|
+
metadata: string | null;
|
|
72
|
+
}): number;
|
|
73
|
+
getById(id: number): SynapseRecord | undefined;
|
|
74
|
+
update(id: number, data: Partial<SynapseRecord>): void;
|
|
75
|
+
delete(id: number): void;
|
|
76
|
+
getOutgoing(nodeType: string, nodeId: number): SynapseRecord[];
|
|
77
|
+
getIncoming(nodeType: string, nodeId: number): SynapseRecord[];
|
|
78
|
+
findInactiveSince(cutoffIso: string): SynapseRecord[];
|
|
79
|
+
topByWeight(limit: number): SynapseRecord[];
|
|
80
|
+
topDiverse(perType: number): SynapseRecord[];
|
|
81
|
+
countNodes(): number;
|
|
82
|
+
totalCount(): number;
|
|
83
|
+
avgWeight(): number;
|
|
84
|
+
countByType(): Record<string, number>;
|
|
85
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/synapses/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, it, expect, vi } from 'vitest';
|
|
2
|
+
import { TypedEventBus } from '../events.js';
|
|
3
|
+
describe('TypedEventBus', () => {
|
|
4
|
+
it('emits and receives events', () => {
|
|
5
|
+
const bus = new TypedEventBus();
|
|
6
|
+
const handler = vi.fn();
|
|
7
|
+
bus.on('test:fired', handler);
|
|
8
|
+
bus.emit('test:fired', { value: 42 });
|
|
9
|
+
expect(handler).toHaveBeenCalledWith({ value: 42 });
|
|
10
|
+
});
|
|
11
|
+
it('supports once listener', () => {
|
|
12
|
+
const bus = new TypedEventBus();
|
|
13
|
+
const handler = vi.fn();
|
|
14
|
+
bus.once('test:fired', handler);
|
|
15
|
+
bus.emit('test:fired', { value: 1 });
|
|
16
|
+
bus.emit('test:fired', { value: 2 });
|
|
17
|
+
expect(handler).toHaveBeenCalledTimes(1);
|
|
18
|
+
});
|
|
19
|
+
it('supports off to remove listener', () => {
|
|
20
|
+
const bus = new TypedEventBus();
|
|
21
|
+
const handler = vi.fn();
|
|
22
|
+
bus.on('test:fired', handler);
|
|
23
|
+
bus.off('test:fired', handler);
|
|
24
|
+
bus.emit('test:fired', { value: 1 });
|
|
25
|
+
expect(handler).not.toHaveBeenCalled();
|
|
26
|
+
});
|
|
27
|
+
it('handles multiple event types independently', () => {
|
|
28
|
+
const bus = new TypedEventBus();
|
|
29
|
+
const handler1 = vi.fn();
|
|
30
|
+
const handler2 = vi.fn();
|
|
31
|
+
bus.on('test:fired', handler1);
|
|
32
|
+
bus.on('test:other', handler2);
|
|
33
|
+
bus.emit('test:fired', { value: 10 });
|
|
34
|
+
expect(handler1).toHaveBeenCalledTimes(1);
|
|
35
|
+
expect(handler2).not.toHaveBeenCalled();
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
//# sourceMappingURL=events.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"events.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/events.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,QAAQ,CAAC;AAClD,OAAO,EAAE,aAAa,EAAE,MAAM,cAAc,CAAC;AAO7C,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,2BAA2B,EAAE,GAAG,EAAE;QACnC,MAAM,GAAG,GAAG,IAAI,aAAa,EAAc,CAAC;QAC5C,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QACxB,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAC9B,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QACtC,MAAM,CAAC,OAAO,CAAC,CAAC,oBAAoB,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,GAAG,GAAG,IAAI,aAAa,EAAc,CAAC;QAC5C,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QACxB,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAChC,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACrC,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACrC,MAAM,CAAC,OAAO,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;IAC3C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,iCAAiC,EAAE,GAAG,EAAE;QACzC,MAAM,GAAG,GAAG,IAAI,aAAa,EAAc,CAAC;QAC5C,MAAM,OAAO,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QACxB,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAC9B,GAAG,CAAC,GAAG,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QAC/B,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,CAAC;QACrC,MAAM,CAAC,OAAO,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IACzC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,4CAA4C,EAAE,GAAG,EAAE;QACpD,MAAM,GAAG,GAAG,IAAI,aAAa,EAAc,CAAC;QAC5C,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QACzB,MAAM,QAAQ,GAAG,EAAE,CAAC,EAAE,EAAE,CAAC;QACzB,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QAC/B,GAAG,CAAC,EAAE,CAAC,YAAY,EAAE,QAAQ,CAAC,CAAC;QAC/B,GAAG,CAAC,IAAI,CAAC,YAAY,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QACtC,MAAM,CAAC,QAAQ,CAAC,CAAC,qBAAqB,CAAC,CAAC,CAAC,CAAC;QAC1C,MAAM,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { describe, it, expect } from 'vitest';
|
|
2
|
+
import { sha256 } from '../hash.js';
|
|
3
|
+
describe('sha256', () => {
|
|
4
|
+
it('returns consistent hash for same input', () => {
|
|
5
|
+
expect(sha256('hello')).toBe(sha256('hello'));
|
|
6
|
+
});
|
|
7
|
+
it('returns different hashes for different inputs', () => {
|
|
8
|
+
expect(sha256('hello')).not.toBe(sha256('world'));
|
|
9
|
+
});
|
|
10
|
+
it('returns 64-char hex string', () => {
|
|
11
|
+
expect(sha256('test')).toMatch(/^[a-f0-9]{64}$/);
|
|
12
|
+
});
|
|
13
|
+
it('matches known SHA-256 value', () => {
|
|
14
|
+
expect(sha256('hello')).toBe('2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824');
|
|
15
|
+
});
|
|
16
|
+
it('handles empty string', () => {
|
|
17
|
+
expect(sha256('')).toBe('e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855');
|
|
18
|
+
});
|
|
19
|
+
it('handles unicode', () => {
|
|
20
|
+
const hash = sha256('こんにちは');
|
|
21
|
+
expect(hash).toMatch(/^[a-f0-9]{64}$/);
|
|
22
|
+
expect(hash).toBe(sha256('こんにちは'));
|
|
23
|
+
});
|
|
24
|
+
});
|
|
25
|
+
//# sourceMappingURL=hash.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hash.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/hash.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAC;AAC9C,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACtB,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,+CAA+C,EAAE,GAAG,EAAE;QACvD,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACpD,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,4BAA4B,EAAE,GAAG,EAAE;QACpC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;IACnD,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,6BAA6B,EAAE,GAAG,EAAE;QACrC,MAAM,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;IACnG,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,sBAAsB,EAAE,GAAG,EAAE;QAC9B,MAAM,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC;IAC9F,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,iBAAiB,EAAE,GAAG,EAAE;QACzB,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,CAAC,CAAC;QAC7B,MAAM,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,gBAAgB,CAAC,CAAC;QACvC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC;IACrC,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { createLogger, getLogger, resetLogger } from '../logger.js';
|
|
3
|
+
describe('logger', () => {
|
|
4
|
+
afterEach(() => {
|
|
5
|
+
resetLogger();
|
|
6
|
+
});
|
|
7
|
+
it('createLogger returns a logger instance', () => {
|
|
8
|
+
const logger = createLogger({ level: 'error' });
|
|
9
|
+
expect(logger).toBeDefined();
|
|
10
|
+
expect(typeof logger.info).toBe('function');
|
|
11
|
+
expect(typeof logger.error).toBe('function');
|
|
12
|
+
});
|
|
13
|
+
it('getLogger returns the same singleton', () => {
|
|
14
|
+
const a = createLogger({ level: 'error' });
|
|
15
|
+
const b = getLogger();
|
|
16
|
+
expect(a).toBe(b);
|
|
17
|
+
});
|
|
18
|
+
it('resetLogger allows creating a new logger', () => {
|
|
19
|
+
const a = createLogger({ level: 'error' });
|
|
20
|
+
resetLogger();
|
|
21
|
+
const b = createLogger({ level: 'warn' });
|
|
22
|
+
expect(a).not.toBe(b);
|
|
23
|
+
});
|
|
24
|
+
it('getLogger auto-creates if none exists', () => {
|
|
25
|
+
const logger = getLogger();
|
|
26
|
+
expect(logger).toBeDefined();
|
|
27
|
+
});
|
|
28
|
+
});
|
|
29
|
+
//# sourceMappingURL=logger.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/logger.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,QAAQ,CAAC;AACzD,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAEpE,QAAQ,CAAC,QAAQ,EAAE,GAAG,EAAE;IACtB,SAAS,CAAC,GAAG,EAAE;QACb,WAAW,EAAE,CAAC;IAChB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,wCAAwC,EAAE,GAAG,EAAE;QAChD,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QAChD,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;QAC7B,MAAM,CAAC,OAAO,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC5C,MAAM,CAAC,OAAO,MAAM,CAAC,KAAK,CAAC,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IAC/C,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,GAAG,EAAE;QAC9C,MAAM,CAAC,GAAG,YAAY,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QAC3C,MAAM,CAAC,GAAG,SAAS,EAAE,CAAC;QACtB,MAAM,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,0CAA0C,EAAE,GAAG,EAAE;QAClD,MAAM,CAAC,GAAG,YAAY,CAAC,EAAE,KAAK,EAAE,OAAO,EAAE,CAAC,CAAC;QAC3C,WAAW,EAAE,CAAC;QACd,MAAM,CAAC,GAAG,YAAY,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;QAC1C,MAAM,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IACxB,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uCAAuC,EAAE,GAAG,EAAE;QAC/C,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,CAAC,MAAM,CAAC,CAAC,WAAW,EAAE,CAAC;IAC/B,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { describe, it, expect, afterEach } from 'vitest';
|
|
2
|
+
import { normalizePath, getDataDir, getPipeName } from '../paths.js';
|
|
3
|
+
describe('normalizePath', () => {
|
|
4
|
+
it('converts backslashes to forward slashes', () => {
|
|
5
|
+
expect(normalizePath('C:\\Users\\test\\file.ts')).toBe('C:/Users/test/file.ts');
|
|
6
|
+
});
|
|
7
|
+
it('leaves forward slashes unchanged', () => {
|
|
8
|
+
expect(normalizePath('/home/user/file.ts')).toBe('/home/user/file.ts');
|
|
9
|
+
});
|
|
10
|
+
it('handles mixed slashes', () => {
|
|
11
|
+
expect(normalizePath('C:\\Users/test\\file.ts')).toBe('C:/Users/test/file.ts');
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
describe('getDataDir', () => {
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
delete process.env['TEST_DATA_DIR'];
|
|
17
|
+
});
|
|
18
|
+
it('uses env var when set', () => {
|
|
19
|
+
process.env['TEST_DATA_DIR'] = '/custom/dir';
|
|
20
|
+
const result = getDataDir('TEST_DATA_DIR', '.brain');
|
|
21
|
+
// path.resolve normalises to platform-native form
|
|
22
|
+
expect(result).toContain('custom');
|
|
23
|
+
expect(result).not.toContain('.brain');
|
|
24
|
+
});
|
|
25
|
+
it('falls back to home dir', () => {
|
|
26
|
+
const result = getDataDir('NONEXISTENT_VAR_XYZ', '.test-brain');
|
|
27
|
+
expect(result).toContain('.test-brain');
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
describe('getPipeName', () => {
|
|
31
|
+
it('returns platform-specific path', () => {
|
|
32
|
+
const name = getPipeName('test-brain');
|
|
33
|
+
if (process.platform === 'win32') {
|
|
34
|
+
expect(name).toBe('\\\\.\\pipe\\test-brain');
|
|
35
|
+
}
|
|
36
|
+
else {
|
|
37
|
+
expect(name).toContain('test-brain.sock');
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
it('defaults to brain', () => {
|
|
41
|
+
const name = getPipeName();
|
|
42
|
+
if (process.platform === 'win32') {
|
|
43
|
+
expect(name).toBe('\\\\.\\pipe\\brain');
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
expect(name).toContain('brain.sock');
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
//# sourceMappingURL=paths.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paths.test.js","sourceRoot":"","sources":["../../../src/utils/__tests__/paths.test.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,EAAE,EAAE,MAAM,EAAM,SAAS,EAAE,MAAM,QAAQ,CAAC;AAC7D,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAErE,QAAQ,CAAC,eAAe,EAAE,GAAG,EAAE;IAC7B,EAAE,CAAC,yCAAyC,EAAE,GAAG,EAAE;QACjD,MAAM,CAAC,aAAa,CAAC,0BAA0B,CAAC,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IAClF,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,kCAAkC,EAAE,GAAG,EAAE;QAC1C,MAAM,CAAC,aAAa,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;IACzE,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,MAAM,CAAC,aAAa,CAAC,yBAAyB,CAAC,CAAC,CAAC,IAAI,CAAC,uBAAuB,CAAC,CAAC;IACjF,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,YAAY,EAAE,GAAG,EAAE;IAC1B,SAAS,CAAC,GAAG,EAAE;QACb,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,CAAC;IACtC,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,uBAAuB,EAAE,GAAG,EAAE;QAC/B,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,GAAG,aAAa,CAAC;QAC7C,MAAM,MAAM,GAAG,UAAU,CAAC,eAAe,EAAE,QAAQ,CAAC,CAAC;QACrD,kDAAkD;QAClD,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;QACnC,MAAM,CAAC,MAAM,CAAC,CAAC,GAAG,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;IACzC,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,wBAAwB,EAAE,GAAG,EAAE;QAChC,MAAM,MAAM,GAAG,UAAU,CAAC,qBAAqB,EAAE,aAAa,CAAC,CAAC;QAChE,MAAM,CAAC,MAAM,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC;IAC1C,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC;AAEH,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,EAAE,CAAC,gCAAgC,EAAE,GAAG,EAAE;QACxC,MAAM,IAAI,GAAG,WAAW,CAAC,YAAY,CAAC,CAAC;QACvC,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;QAC/C,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,iBAAiB,CAAC,CAAC;QAC5C,CAAC;IACH,CAAC,CAAC,CAAC;IACH,EAAE,CAAC,mBAAmB,EAAE,GAAG,EAAE;QAC3B,MAAM,IAAI,GAAG,WAAW,EAAE,CAAC;QAC3B,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,MAAM,CAAC,IAAI,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;QAC1C,CAAC;aAAM,CAAC;YACN,MAAM,CAAC,IAAI,CAAC,CAAC,SAAS,CAAC,YAAY,CAAC,CAAC;QACvC,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/eslint.config.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import tseslint from 'typescript-eslint';
|
|
2
|
+
|
|
3
|
+
export default tseslint.config(
|
|
4
|
+
...tseslint.configs.recommended,
|
|
5
|
+
{
|
|
6
|
+
ignores: ['dist/', 'node_modules/', '**/*.js', '**/*.d.ts'],
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
rules: {
|
|
10
|
+
'@typescript-eslint/no-explicit-any': 'warn',
|
|
11
|
+
'@typescript-eslint/no-unused-vars': ['warn', { argsIgnorePattern: '^_' }],
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timmeck/brain-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.5.0",
|
|
4
4
|
"description": "Shared core infrastructure for the Brain ecosystem — IPC, MCP, CLI, DB connection, and utilities",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -20,12 +20,24 @@
|
|
|
20
20
|
"./mcp/http-server": "./dist/mcp/http-server.js",
|
|
21
21
|
"./cli/colors": "./dist/cli/colors.js",
|
|
22
22
|
"./api/server": "./dist/api/server.js",
|
|
23
|
+
"./math/wilson-score": "./dist/math/wilson-score.js",
|
|
24
|
+
"./math/time-decay": "./dist/math/time-decay.js",
|
|
25
|
+
"./config/loader": "./dist/config/loader.js",
|
|
26
|
+
"./synapses/types": "./dist/synapses/types.js",
|
|
27
|
+
"./synapses/hebbian": "./dist/synapses/hebbian.js",
|
|
28
|
+
"./synapses/decay": "./dist/synapses/decay.js",
|
|
29
|
+
"./synapses/activation": "./dist/synapses/activation.js",
|
|
30
|
+
"./synapses/pathfinder": "./dist/synapses/pathfinder.js",
|
|
31
|
+
"./synapses/synapse-manager": "./dist/synapses/synapse-manager.js",
|
|
23
32
|
"./cross-brain": "./dist/cross-brain/client.js"
|
|
24
33
|
},
|
|
25
34
|
"scripts": {
|
|
26
35
|
"build": "tsc",
|
|
27
36
|
"dev": "tsx src/index.ts",
|
|
28
|
-
"test": "vitest"
|
|
37
|
+
"test": "vitest",
|
|
38
|
+
"lint": "eslint src/",
|
|
39
|
+
"lint:fix": "eslint src/ --fix",
|
|
40
|
+
"test:coverage": "vitest --coverage"
|
|
29
41
|
},
|
|
30
42
|
"keywords": [
|
|
31
43
|
"brain",
|
|
@@ -53,8 +65,11 @@
|
|
|
53
65
|
"devDependencies": {
|
|
54
66
|
"@types/better-sqlite3": "^7.6.12",
|
|
55
67
|
"@types/node": "^22.0.0",
|
|
68
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
69
|
+
"eslint": "^9.39.3",
|
|
56
70
|
"tsx": "^4.19.0",
|
|
57
71
|
"typescript": "^5.7.0",
|
|
72
|
+
"typescript-eslint": "^8.56.1",
|
|
58
73
|
"vitest": "^3.0.0"
|
|
59
74
|
}
|
|
60
75
|
}
|
package/.github/FUNDING.yml
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
github: timmeck
|
package/.github/workflows/ci.yml
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
name: CI
|
|
2
|
-
|
|
3
|
-
on:
|
|
4
|
-
push:
|
|
5
|
-
branches: [master, main]
|
|
6
|
-
pull_request:
|
|
7
|
-
branches: [master, main]
|
|
8
|
-
|
|
9
|
-
jobs:
|
|
10
|
-
build:
|
|
11
|
-
runs-on: ubuntu-latest
|
|
12
|
-
steps:
|
|
13
|
-
- uses: actions/checkout@v4
|
|
14
|
-
- uses: actions/setup-node@v4
|
|
15
|
-
with:
|
|
16
|
-
node-version: 20
|
|
17
|
-
cache: npm
|
|
18
|
-
- run: npm ci
|
|
19
|
-
- run: npm run build
|
|
20
|
-
- run: npm test
|
|
21
|
-
if: ${{ hashFiles('vitest.config.*', 'src/**/*.test.ts', 'tests/**/*.test.ts') != '' }}
|
package/src/api/server.ts
DELETED
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import http from 'node:http';
|
|
2
|
-
import { getLogger } from '../utils/logger.js';
|
|
3
|
-
import type { IpcRouter } from '../ipc/server.js';
|
|
4
|
-
|
|
5
|
-
export interface ApiServerOptions {
|
|
6
|
-
port: number;
|
|
7
|
-
router: IpcRouter;
|
|
8
|
-
apiKey?: string;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export interface RouteDefinition {
|
|
12
|
-
method: string;
|
|
13
|
-
pattern: RegExp;
|
|
14
|
-
ipcMethod: string;
|
|
15
|
-
extractParams: (match: RegExpMatchArray, query: URLSearchParams, body?: unknown) => unknown;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
export class BaseApiServer {
|
|
19
|
-
private server: http.Server | null = null;
|
|
20
|
-
protected logger = getLogger();
|
|
21
|
-
private routes: RouteDefinition[];
|
|
22
|
-
protected sseClients: Set<http.ServerResponse> = new Set();
|
|
23
|
-
|
|
24
|
-
constructor(protected options: ApiServerOptions) {
|
|
25
|
-
this.routes = this.buildRoutes();
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
start(): void {
|
|
29
|
-
const { port, apiKey } = this.options;
|
|
30
|
-
|
|
31
|
-
this.server = http.createServer((req, res) => {
|
|
32
|
-
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
33
|
-
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
|
|
34
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key');
|
|
35
|
-
|
|
36
|
-
if (req.method === 'OPTIONS') {
|
|
37
|
-
res.writeHead(204);
|
|
38
|
-
res.end();
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (apiKey) {
|
|
43
|
-
const provided = (req.headers['x-api-key'] as string) ??
|
|
44
|
-
req.headers.authorization?.replace('Bearer ', '');
|
|
45
|
-
if (provided !== apiKey) {
|
|
46
|
-
this.json(res, 401, { error: 'Unauthorized', message: 'Invalid or missing API key' });
|
|
47
|
-
return;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
this.handleRequest(req, res).catch((err) => {
|
|
52
|
-
this.logger.error('API error:', err);
|
|
53
|
-
this.json(res, 500, {
|
|
54
|
-
error: 'Internal Server Error',
|
|
55
|
-
message: err instanceof Error ? err.message : String(err),
|
|
56
|
-
});
|
|
57
|
-
});
|
|
58
|
-
});
|
|
59
|
-
|
|
60
|
-
this.server.listen(port, () => {
|
|
61
|
-
this.logger.info(`REST API server started on http://localhost:${port}`);
|
|
62
|
-
});
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
stop(): void {
|
|
66
|
-
for (const client of this.sseClients) {
|
|
67
|
-
try { client.end(); } catch { /* ignore */ }
|
|
68
|
-
}
|
|
69
|
-
this.sseClients.clear();
|
|
70
|
-
this.server?.close();
|
|
71
|
-
this.server = null;
|
|
72
|
-
this.logger.info('REST API server stopped');
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/** Override to add domain-specific RESTful routes */
|
|
76
|
-
protected buildRoutes(): RouteDefinition[] {
|
|
77
|
-
return [];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
private async handleRequest(req: http.IncomingMessage, res: http.ServerResponse): Promise<void> {
|
|
81
|
-
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
82
|
-
const pathname = url.pathname;
|
|
83
|
-
const method = req.method ?? 'GET';
|
|
84
|
-
const query = url.searchParams;
|
|
85
|
-
|
|
86
|
-
// Health check
|
|
87
|
-
if (pathname === '/api/v1/health') {
|
|
88
|
-
this.json(res, 200, { status: 'ok', timestamp: new Date().toISOString() });
|
|
89
|
-
return;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
// SSE event stream
|
|
93
|
-
if (pathname === '/api/v1/events' && method === 'GET') {
|
|
94
|
-
res.writeHead(200, {
|
|
95
|
-
'Content-Type': 'text/event-stream',
|
|
96
|
-
'Cache-Control': 'no-cache',
|
|
97
|
-
'Connection': 'keep-alive',
|
|
98
|
-
});
|
|
99
|
-
res.write('data: {"type":"connected"}\n\n');
|
|
100
|
-
this.sseClients.add(res);
|
|
101
|
-
req.on('close', () => this.sseClients.delete(res));
|
|
102
|
-
return;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
// List all available methods
|
|
106
|
-
if (pathname === '/api/v1/methods' && method === 'GET') {
|
|
107
|
-
const methods = this.options.router.listMethods();
|
|
108
|
-
this.json(res, 200, {
|
|
109
|
-
methods,
|
|
110
|
-
rpcEndpoint: '/api/v1/rpc',
|
|
111
|
-
usage: 'POST /api/v1/rpc with body { "method": "<method>", "params": {...} }',
|
|
112
|
-
});
|
|
113
|
-
return;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// Generic RPC endpoint
|
|
117
|
-
if (pathname === '/api/v1/rpc' && method === 'POST') {
|
|
118
|
-
const body = await this.readBody(req);
|
|
119
|
-
if (!body) {
|
|
120
|
-
this.json(res, 400, { error: 'Bad Request', message: 'Empty request body' });
|
|
121
|
-
return;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
const parsed = JSON.parse(body);
|
|
125
|
-
|
|
126
|
-
// Batch RPC support
|
|
127
|
-
if (Array.isArray(parsed)) {
|
|
128
|
-
const results = parsed.map((call: { method: string; params?: unknown; id?: string | number }) => {
|
|
129
|
-
try {
|
|
130
|
-
const result = this.options.router.handle(call.method, call.params ?? {});
|
|
131
|
-
return { id: call.id, result };
|
|
132
|
-
} catch (err) {
|
|
133
|
-
return { id: call.id, error: err instanceof Error ? err.message : String(err) };
|
|
134
|
-
}
|
|
135
|
-
});
|
|
136
|
-
this.json(res, 200, results);
|
|
137
|
-
return;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (!parsed.method) {
|
|
141
|
-
this.json(res, 400, { error: 'Bad Request', message: 'Missing "method" field' });
|
|
142
|
-
return;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
try {
|
|
146
|
-
const result = this.options.router.handle(parsed.method, parsed.params ?? {});
|
|
147
|
-
this.json(res, 200, { result });
|
|
148
|
-
} catch (err) {
|
|
149
|
-
this.json(res, 400, { error: err instanceof Error ? err.message : String(err) });
|
|
150
|
-
}
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
// RESTful routes
|
|
155
|
-
let body: unknown = undefined;
|
|
156
|
-
if (method === 'POST' || method === 'PUT') {
|
|
157
|
-
try {
|
|
158
|
-
const raw = await this.readBody(req);
|
|
159
|
-
body = raw ? JSON.parse(raw) : {};
|
|
160
|
-
} catch {
|
|
161
|
-
this.json(res, 400, { error: 'Bad Request', message: 'Invalid JSON body' });
|
|
162
|
-
return;
|
|
163
|
-
}
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
for (const route of this.routes) {
|
|
167
|
-
if (route.method !== method) continue;
|
|
168
|
-
const match = pathname.match(route.pattern);
|
|
169
|
-
if (!match) continue;
|
|
170
|
-
|
|
171
|
-
try {
|
|
172
|
-
const params = route.extractParams(match, query, body);
|
|
173
|
-
const result = this.options.router.handle(route.ipcMethod, params);
|
|
174
|
-
this.json(res, method === 'POST' ? 201 : 200, { result });
|
|
175
|
-
} catch (err) {
|
|
176
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
177
|
-
const status = msg.startsWith('Unknown method') ? 404 : 400;
|
|
178
|
-
this.json(res, status, { error: msg });
|
|
179
|
-
}
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
this.json(res, 404, { error: 'Not Found', message: `No route for ${method} ${pathname}` });
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
protected json(res: http.ServerResponse, status: number, data: unknown): void {
|
|
187
|
-
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
188
|
-
res.end(JSON.stringify(data));
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
protected readBody(req: http.IncomingMessage): Promise<string> {
|
|
192
|
-
return new Promise((resolve, reject) => {
|
|
193
|
-
const chunks: Buffer[] = [];
|
|
194
|
-
req.on('data', (chunk: Buffer) => chunks.push(chunk));
|
|
195
|
-
req.on('end', () => resolve(Buffer.concat(chunks).toString('utf8')));
|
|
196
|
-
req.on('error', reject);
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
protected broadcastSSE(data: unknown): void {
|
|
201
|
-
const msg = `data: ${JSON.stringify(data)}\n\n`;
|
|
202
|
-
for (const client of this.sseClients) {
|
|
203
|
-
try {
|
|
204
|
-
client.write(msg);
|
|
205
|
-
} catch {
|
|
206
|
-
this.sseClients.delete(client);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|