@timmeck/brain-core 2.0.4 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/middleware.d.ts +62 -0
- package/dist/api/middleware.js +107 -0
- package/dist/api/middleware.js.map +1 -0
- package/dist/api/server.d.ts +7 -0
- package/dist/api/server.js +52 -14
- package/dist/api/server.js.map +1 -1
- package/dist/cross-brain/correlator.d.ts +92 -0
- package/dist/cross-brain/correlator.js +248 -0
- package/dist/cross-brain/correlator.js.map +1 -0
- package/dist/dashboard/hub-server.d.ts +9 -0
- package/dist/dashboard/hub-server.js +42 -0
- package/dist/dashboard/hub-server.js.map +1 -0
- package/dist/ecosystem/service.d.ts +64 -0
- package/dist/ecosystem/service.js +106 -0
- package/dist/ecosystem/service.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -1
- package/dist/ipc/errors.d.ts +32 -0
- package/dist/ipc/errors.js +50 -0
- package/dist/ipc/errors.js.map +1 -0
- package/dist/ipc/validation.d.ts +15 -0
- package/dist/ipc/validation.js +62 -0
- package/dist/ipc/validation.js.map +1 -0
- package/hub-dashboard.html +859 -0
- package/package.json +7 -1
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
export interface RateLimitConfig {
|
|
2
|
+
windowMs?: number;
|
|
3
|
+
maxRequests?: number;
|
|
4
|
+
keyExtractor?: (req: {
|
|
5
|
+
socket: {
|
|
6
|
+
remoteAddress?: string;
|
|
7
|
+
};
|
|
8
|
+
}) => string;
|
|
9
|
+
}
|
|
10
|
+
export declare class RateLimiter {
|
|
11
|
+
private store;
|
|
12
|
+
private windowMs;
|
|
13
|
+
private maxRequests;
|
|
14
|
+
private keyExtractor;
|
|
15
|
+
private cleanupTimer;
|
|
16
|
+
constructor(config?: RateLimitConfig);
|
|
17
|
+
/**
|
|
18
|
+
* Check if a request is allowed. Returns { allowed, remaining, resetAt }.
|
|
19
|
+
*/
|
|
20
|
+
check(req: {
|
|
21
|
+
socket: {
|
|
22
|
+
remoteAddress?: string;
|
|
23
|
+
};
|
|
24
|
+
}): {
|
|
25
|
+
allowed: boolean;
|
|
26
|
+
remaining: number;
|
|
27
|
+
resetAt: number;
|
|
28
|
+
};
|
|
29
|
+
/** Reset a specific key (for testing) */
|
|
30
|
+
reset(key: string): void;
|
|
31
|
+
/** Clear all entries */
|
|
32
|
+
clear(): void;
|
|
33
|
+
/** Stop cleanup timer */
|
|
34
|
+
stop(): void;
|
|
35
|
+
private cleanup;
|
|
36
|
+
}
|
|
37
|
+
export interface SizeLimitConfig {
|
|
38
|
+
maxBodyBytes?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Read request body with size limit enforcement.
|
|
42
|
+
* Returns the body string or null if the limit is exceeded.
|
|
43
|
+
*/
|
|
44
|
+
export declare function readBodyWithLimit(req: import('node:http').IncomingMessage, config?: SizeLimitConfig): Promise<{
|
|
45
|
+
body: string;
|
|
46
|
+
error?: undefined;
|
|
47
|
+
} | {
|
|
48
|
+
body?: undefined;
|
|
49
|
+
error: string;
|
|
50
|
+
}>;
|
|
51
|
+
export interface SecurityHeadersConfig {
|
|
52
|
+
cors?: {
|
|
53
|
+
origins?: string[];
|
|
54
|
+
methods?: string[];
|
|
55
|
+
headers?: string[];
|
|
56
|
+
};
|
|
57
|
+
hsts?: boolean;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Apply security headers to an HTTP response.
|
|
61
|
+
*/
|
|
62
|
+
export declare function applySecurityHeaders(res: import('node:http').ServerResponse, config?: SecurityHeadersConfig): void;
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export class RateLimiter {
|
|
2
|
+
store = new Map();
|
|
3
|
+
windowMs;
|
|
4
|
+
maxRequests;
|
|
5
|
+
keyExtractor;
|
|
6
|
+
cleanupTimer;
|
|
7
|
+
constructor(config) {
|
|
8
|
+
this.windowMs = config?.windowMs ?? 60_000;
|
|
9
|
+
this.maxRequests = config?.maxRequests ?? 100;
|
|
10
|
+
this.keyExtractor = config?.keyExtractor ?? ((req) => req.socket.remoteAddress ?? 'unknown');
|
|
11
|
+
// Cleanup expired entries every minute
|
|
12
|
+
this.cleanupTimer = setInterval(() => this.cleanup(), 60_000);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Check if a request is allowed. Returns { allowed, remaining, resetAt }.
|
|
16
|
+
*/
|
|
17
|
+
check(req) {
|
|
18
|
+
const key = this.keyExtractor(req);
|
|
19
|
+
const now = Date.now();
|
|
20
|
+
const entry = this.store.get(key);
|
|
21
|
+
if (!entry || now >= entry.resetAt) {
|
|
22
|
+
this.store.set(key, { count: 1, resetAt: now + this.windowMs });
|
|
23
|
+
return { allowed: true, remaining: this.maxRequests - 1, resetAt: now + this.windowMs };
|
|
24
|
+
}
|
|
25
|
+
entry.count++;
|
|
26
|
+
if (entry.count > this.maxRequests) {
|
|
27
|
+
return { allowed: false, remaining: 0, resetAt: entry.resetAt };
|
|
28
|
+
}
|
|
29
|
+
return { allowed: true, remaining: this.maxRequests - entry.count, resetAt: entry.resetAt };
|
|
30
|
+
}
|
|
31
|
+
/** Reset a specific key (for testing) */
|
|
32
|
+
reset(key) {
|
|
33
|
+
this.store.delete(key);
|
|
34
|
+
}
|
|
35
|
+
/** Clear all entries */
|
|
36
|
+
clear() {
|
|
37
|
+
this.store.clear();
|
|
38
|
+
}
|
|
39
|
+
/** Stop cleanup timer */
|
|
40
|
+
stop() {
|
|
41
|
+
clearInterval(this.cleanupTimer);
|
|
42
|
+
}
|
|
43
|
+
cleanup() {
|
|
44
|
+
const now = Date.now();
|
|
45
|
+
for (const [key, entry] of this.store) {
|
|
46
|
+
if (now >= entry.resetAt) {
|
|
47
|
+
this.store.delete(key);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Read request body with size limit enforcement.
|
|
54
|
+
* Returns the body string or null if the limit is exceeded.
|
|
55
|
+
*/
|
|
56
|
+
export function readBodyWithLimit(req, config) {
|
|
57
|
+
const maxBytes = config?.maxBodyBytes ?? 102_400;
|
|
58
|
+
return new Promise((resolve) => {
|
|
59
|
+
const chunks = [];
|
|
60
|
+
let totalBytes = 0;
|
|
61
|
+
let aborted = false;
|
|
62
|
+
req.on('data', (chunk) => {
|
|
63
|
+
if (aborted)
|
|
64
|
+
return;
|
|
65
|
+
totalBytes += chunk.length;
|
|
66
|
+
if (totalBytes > maxBytes) {
|
|
67
|
+
aborted = true;
|
|
68
|
+
req.destroy();
|
|
69
|
+
resolve({ error: `Request body exceeds limit of ${maxBytes} bytes` });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
chunks.push(chunk);
|
|
73
|
+
});
|
|
74
|
+
req.on('end', () => {
|
|
75
|
+
if (aborted)
|
|
76
|
+
return;
|
|
77
|
+
resolve({ body: Buffer.concat(chunks).toString('utf8') });
|
|
78
|
+
});
|
|
79
|
+
req.on('error', () => {
|
|
80
|
+
if (aborted)
|
|
81
|
+
return;
|
|
82
|
+
resolve({ error: 'Request read error' });
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Apply security headers to an HTTP response.
|
|
88
|
+
*/
|
|
89
|
+
export function applySecurityHeaders(res, config) {
|
|
90
|
+
// CORS
|
|
91
|
+
const origins = config?.cors?.origins ?? ['*'];
|
|
92
|
+
const methods = config?.cors?.methods ?? ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'];
|
|
93
|
+
const headers = config?.cors?.headers ?? ['Content-Type', 'Authorization', 'X-API-Key'];
|
|
94
|
+
res.setHeader('Access-Control-Allow-Origin', origins.join(', '));
|
|
95
|
+
res.setHeader('Access-Control-Allow-Methods', methods.join(', '));
|
|
96
|
+
res.setHeader('Access-Control-Allow-Headers', headers.join(', '));
|
|
97
|
+
// Security headers
|
|
98
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
99
|
+
res.setHeader('X-Frame-Options', 'DENY');
|
|
100
|
+
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
101
|
+
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
102
|
+
// HSTS (only when explicitly enabled — requires HTTPS)
|
|
103
|
+
if (config?.hsts) {
|
|
104
|
+
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/api/middleware.ts"],"names":[],"mappings":"AAeA,MAAM,OAAO,WAAW;IACd,KAAK,GAAgC,IAAI,GAAG,EAAE,CAAC;IAC/C,QAAQ,CAAS;IACjB,WAAW,CAAS;IACpB,YAAY,CAA0D;IACtE,YAAY,CAAiC;IAErD,YAAY,MAAwB;QAClC,IAAI,CAAC,QAAQ,GAAG,MAAM,EAAE,QAAQ,IAAI,MAAM,CAAC;QAC3C,IAAI,CAAC,WAAW,GAAG,MAAM,EAAE,WAAW,IAAI,GAAG,CAAC;QAC9C,IAAI,CAAC,YAAY,GAAG,MAAM,EAAE,YAAY,IAAI,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,CAAC,MAAM,CAAC,aAAa,IAAI,SAAS,CAAC,CAAC;QAC7F,uCAAuC;QACvC,IAAI,CAAC,YAAY,GAAG,WAAW,CAAC,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,CAAC,CAAC;IAChE,CAAC;IAED;;OAEG;IACH,KAAK,CAAC,GAA2C;QAC/C,MAAM,GAAG,GAAG,IAAI,CAAC,YAAY,CAAC,GAAG,CAAC,CAAC;QACnC,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAElC,IAAI,CAAC,KAAK,IAAI,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;YACnC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;YAChE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,WAAW,GAAG,CAAC,EAAE,OAAO,EAAE,GAAG,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QAC1F,CAAC;QAED,KAAK,CAAC,KAAK,EAAE,CAAC;QACd,IAAI,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;YACnC,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;QAClE,CAAC;QAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,CAAC,WAAW,GAAG,KAAK,CAAC,KAAK,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC;IAC9F,CAAC;IAED,yCAAyC;IACzC,KAAK,CAAC,GAAW;QACf,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACzB,CAAC;IAED,wBAAwB;IACxB,KAAK;QACH,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;IACrB,CAAC;IAED,yBAAyB;IACzB,IAAI;QACF,aAAa,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACnC,CAAC;IAEO,OAAO;QACb,MAAM,GAAG,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACvB,KAAK,MAAM,CAAC,GAAG,EAAE,KAAK,CAAC,IAAI,IAAI,CAAC,KAAK,EAAE,CAAC;YACtC,IAAI,GAAG,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;gBACzB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;YACzB,CAAC;QACH,CAAC;IACH,CAAC;CACF;AAQD;;;GAGG;AACH,MAAM,UAAU,iBAAiB,CAC/B,GAAwC,EACxC,MAAwB;IAExB,MAAM,QAAQ,GAAG,MAAM,EAAE,YAAY,IAAI,OAAO,CAAC;IAEjD,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,MAAM,MAAM,GAAa,EAAE,CAAC;QAC5B,IAAI,UAAU,GAAG,CAAC,CAAC;QACnB,IAAI,OAAO,GAAG,KAAK,CAAC;QAEpB,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE;YAC/B,IAAI,OAAO;gBAAE,OAAO;YACpB,UAAU,IAAI,KAAK,CAAC,MAAM,CAAC;YAC3B,IAAI,UAAU,GAAG,QAAQ,EAAE,CAAC;gBAC1B,OAAO,GAAG,IAAI,CAAC;gBACf,GAAG,CAAC,OAAO,EAAE,CAAC;gBACd,OAAO,CAAC,EAAE,KAAK,EAAE,iCAAiC,QAAQ,QAAQ,EAAE,CAAC,CAAC;gBACtE,OAAO;YACT,CAAC;YACD,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;QACrB,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE;YACjB,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,CAAC,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC;QAC5D,CAAC,CAAC,CAAC;QAEH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;YACnB,IAAI,OAAO;gBAAE,OAAO;YACpB,OAAO,CAAC,EAAE,KAAK,EAAE,oBAAoB,EAAE,CAAC,CAAC;QAC3C,CAAC,CAAC,CAAC;IACL,CAAC,CAAC,CAAC;AACL,CAAC;AAaD;;GAEG;AACH,MAAM,UAAU,oBAAoB,CAClC,GAAuC,EACvC,MAA8B;IAE9B,OAAO;IACP,MAAM,OAAO,GAAG,MAAM,EAAE,IAAI,EAAE,OAAO,IAAI,CAAC,GAAG,CAAC,CAAC;IAC/C,MAAM,OAAO,GAAG,MAAM,EAAE,IAAI,EAAE,OAAO,IAAI,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,SAAS,CAAC,CAAC;IACrF,MAAM,OAAO,GAAG,MAAM,EAAE,IAAI,EAAE,OAAO,IAAI,CAAC,cAAc,EAAE,eAAe,EAAE,WAAW,CAAC,CAAC;IAExF,GAAG,CAAC,SAAS,CAAC,6BAA6B,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IACjE,GAAG,CAAC,SAAS,CAAC,8BAA8B,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAClE,GAAG,CAAC,SAAS,CAAC,8BAA8B,EAAE,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;IAElE,mBAAmB;IACnB,GAAG,CAAC,SAAS,CAAC,wBAAwB,EAAE,SAAS,CAAC,CAAC;IACnD,GAAG,CAAC,SAAS,CAAC,iBAAiB,EAAE,MAAM,CAAC,CAAC;IACzC,GAAG,CAAC,SAAS,CAAC,kBAAkB,EAAE,eAAe,CAAC,CAAC;IACnD,GAAG,CAAC,SAAS,CAAC,iBAAiB,EAAE,iCAAiC,CAAC,CAAC;IAEpE,uDAAuD;IACvD,IAAI,MAAM,EAAE,IAAI,EAAE,CAAC;QACjB,GAAG,CAAC,SAAS,CAAC,2BAA2B,EAAE,qCAAqC,CAAC,CAAC;IACpF,CAAC;AACH,CAAC"}
|
package/dist/api/server.d.ts
CHANGED
|
@@ -1,9 +1,15 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
2
|
import type { IpcRouter } from '../ipc/server.js';
|
|
3
|
+
import type { RateLimitConfig, SizeLimitConfig, SecurityHeadersConfig } from './middleware.js';
|
|
3
4
|
export interface ApiServerOptions {
|
|
4
5
|
port: number;
|
|
5
6
|
router: IpcRouter;
|
|
6
7
|
apiKey?: string;
|
|
8
|
+
rateLimit?: RateLimitConfig;
|
|
9
|
+
sizeLimit?: SizeLimitConfig;
|
|
10
|
+
security?: SecurityHeadersConfig;
|
|
11
|
+
/** Callback for deep health checks (DB, engines, etc.) */
|
|
12
|
+
healthCheck?: () => Record<string, unknown>;
|
|
7
13
|
}
|
|
8
14
|
export interface RouteDefinition {
|
|
9
15
|
method: string;
|
|
@@ -17,6 +23,7 @@ export declare class BaseApiServer {
|
|
|
17
23
|
protected logger: import("winston").Logger;
|
|
18
24
|
private routes;
|
|
19
25
|
protected sseClients: Set<http.ServerResponse>;
|
|
26
|
+
private rateLimiter;
|
|
20
27
|
constructor(options: ApiServerOptions);
|
|
21
28
|
start(): void;
|
|
22
29
|
stop(): void;
|
package/dist/api/server.js
CHANGED
|
@@ -1,26 +1,42 @@
|
|
|
1
1
|
import http from 'node:http';
|
|
2
2
|
import { getLogger } from '../utils/logger.js';
|
|
3
|
+
import { RateLimiter, readBodyWithLimit, applySecurityHeaders } from './middleware.js';
|
|
4
|
+
import { validateParams } from '../ipc/validation.js';
|
|
5
|
+
import { ValidationError } from '../ipc/errors.js';
|
|
3
6
|
export class BaseApiServer {
|
|
4
7
|
options;
|
|
5
8
|
server = null;
|
|
6
9
|
logger = getLogger();
|
|
7
10
|
routes;
|
|
8
11
|
sseClients = new Set();
|
|
12
|
+
rateLimiter;
|
|
9
13
|
constructor(options) {
|
|
10
14
|
this.options = options;
|
|
11
15
|
this.routes = this.buildRoutes();
|
|
16
|
+
this.rateLimiter = new RateLimiter(options.rateLimit);
|
|
12
17
|
}
|
|
13
18
|
start() {
|
|
14
19
|
const { port, apiKey } = this.options;
|
|
15
20
|
this.server = http.createServer((req, res) => {
|
|
16
|
-
|
|
17
|
-
res
|
|
18
|
-
res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-API-Key');
|
|
21
|
+
// Security headers
|
|
22
|
+
applySecurityHeaders(res, this.options.security);
|
|
19
23
|
if (req.method === 'OPTIONS') {
|
|
20
24
|
res.writeHead(204);
|
|
21
25
|
res.end();
|
|
22
26
|
return;
|
|
23
27
|
}
|
|
28
|
+
// Rate limiting (skip health check)
|
|
29
|
+
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
30
|
+
if (url.pathname !== '/api/v1/health' && url.pathname !== '/api/v1/ready') {
|
|
31
|
+
const limit = this.rateLimiter.check(req);
|
|
32
|
+
res.setHeader('X-RateLimit-Remaining', String(limit.remaining));
|
|
33
|
+
res.setHeader('X-RateLimit-Reset', String(Math.ceil(limit.resetAt / 1000)));
|
|
34
|
+
if (!limit.allowed) {
|
|
35
|
+
this.json(res, 429, { error: 'Too Many Requests', message: 'Rate limit exceeded' });
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
// API key auth
|
|
24
40
|
if (apiKey) {
|
|
25
41
|
const provided = req.headers['x-api-key'] ??
|
|
26
42
|
req.headers.authorization?.replace('Bearer ', '');
|
|
@@ -42,6 +58,7 @@ export class BaseApiServer {
|
|
|
42
58
|
});
|
|
43
59
|
}
|
|
44
60
|
stop() {
|
|
61
|
+
this.rateLimiter.stop();
|
|
45
62
|
for (const client of this.sseClients) {
|
|
46
63
|
try {
|
|
47
64
|
client.end();
|
|
@@ -62,9 +79,18 @@ export class BaseApiServer {
|
|
|
62
79
|
const pathname = url.pathname;
|
|
63
80
|
const method = req.method ?? 'GET';
|
|
64
81
|
const query = url.searchParams;
|
|
65
|
-
// Health check
|
|
82
|
+
// Health check (deep)
|
|
66
83
|
if (pathname === '/api/v1/health') {
|
|
67
|
-
|
|
84
|
+
const base = { status: 'ok', timestamp: new Date().toISOString(), uptime: Math.floor(process.uptime()), memory: Math.floor(process.memoryUsage().rss / 1024 / 1024) };
|
|
85
|
+
const extra = this.options.healthCheck?.() ?? {};
|
|
86
|
+
this.json(res, 200, { ...base, ...extra });
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
// Readiness probe (for Kubernetes)
|
|
90
|
+
if (pathname === '/api/v1/ready') {
|
|
91
|
+
const extra = this.options.healthCheck?.() ?? {};
|
|
92
|
+
const ready = extra.db !== false && extra.ipc !== false;
|
|
93
|
+
this.json(res, ready ? 200 : 503, { ready, timestamp: new Date().toISOString(), ...extra });
|
|
68
94
|
return;
|
|
69
95
|
}
|
|
70
96
|
// SSE event stream
|
|
@@ -91,21 +117,27 @@ export class BaseApiServer {
|
|
|
91
117
|
}
|
|
92
118
|
// Generic RPC endpoint
|
|
93
119
|
if (pathname === '/api/v1/rpc' && method === 'POST') {
|
|
94
|
-
const
|
|
95
|
-
if (
|
|
120
|
+
const bodyResult = await readBodyWithLimit(req, this.options.sizeLimit);
|
|
121
|
+
if (bodyResult.error) {
|
|
122
|
+
this.json(res, 413, { error: 'Payload Too Large', message: bodyResult.error });
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (!bodyResult.body) {
|
|
96
126
|
this.json(res, 400, { error: 'Bad Request', message: 'Empty request body' });
|
|
97
127
|
return;
|
|
98
128
|
}
|
|
99
|
-
const parsed = JSON.parse(body);
|
|
129
|
+
const parsed = JSON.parse(bodyResult.body);
|
|
100
130
|
// Batch RPC support
|
|
101
131
|
if (Array.isArray(parsed)) {
|
|
102
132
|
const results = parsed.map((call) => {
|
|
103
133
|
try {
|
|
104
|
-
const
|
|
134
|
+
const validated = validateParams(call.params);
|
|
135
|
+
const result = this.options.router.handle(call.method, validated);
|
|
105
136
|
return { id: call.id, result };
|
|
106
137
|
}
|
|
107
138
|
catch (err) {
|
|
108
|
-
|
|
139
|
+
const code = err instanceof ValidationError ? 'VALIDATION_ERROR' : 'ERROR';
|
|
140
|
+
return { id: call.id, error: err instanceof Error ? err.message : String(err), code };
|
|
109
141
|
}
|
|
110
142
|
});
|
|
111
143
|
this.json(res, 200, results);
|
|
@@ -116,11 +148,13 @@ export class BaseApiServer {
|
|
|
116
148
|
return;
|
|
117
149
|
}
|
|
118
150
|
try {
|
|
119
|
-
const
|
|
151
|
+
const validated = validateParams(parsed.params);
|
|
152
|
+
const result = this.options.router.handle(parsed.method, validated);
|
|
120
153
|
this.json(res, 200, { result });
|
|
121
154
|
}
|
|
122
155
|
catch (err) {
|
|
123
|
-
|
|
156
|
+
const status = err instanceof ValidationError ? 400 : 400;
|
|
157
|
+
this.json(res, status, { error: err instanceof Error ? err.message : String(err) });
|
|
124
158
|
}
|
|
125
159
|
return;
|
|
126
160
|
}
|
|
@@ -128,8 +162,12 @@ export class BaseApiServer {
|
|
|
128
162
|
let body = undefined;
|
|
129
163
|
if (method === 'POST' || method === 'PUT') {
|
|
130
164
|
try {
|
|
131
|
-
const
|
|
132
|
-
|
|
165
|
+
const bodyResult = await readBodyWithLimit(req, this.options.sizeLimit);
|
|
166
|
+
if (bodyResult.error) {
|
|
167
|
+
this.json(res, 413, { error: 'Payload Too Large', message: bodyResult.error });
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
body = bodyResult.body ? JSON.parse(bodyResult.body) : {};
|
|
133
171
|
}
|
|
134
172
|
catch {
|
|
135
173
|
this.json(res, 400, { error: 'Bad Request', message: 'Invalid JSON body' });
|
package/dist/api/server.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../../src/api/server.ts"],"names":[],"mappings":"AAAA,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEvF,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAoBnD,MAAM,OAAO,aAAa;IAOF;IANd,MAAM,GAAuB,IAAI,CAAC;IAChC,MAAM,GAAG,SAAS,EAAE,CAAC;IACvB,MAAM,CAAoB;IACxB,UAAU,GAA6B,IAAI,GAAG,EAAE,CAAC;IACnD,WAAW,CAAc;IAEjC,YAAsB,OAAyB;QAAzB,YAAO,GAAP,OAAO,CAAkB;QAC7C,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QACjC,IAAI,CAAC,WAAW,GAAG,IAAI,WAAW,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,CAAC;IAED,KAAK;QACH,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,GAAG,IAAI,CAAC,OAAO,CAAC;QAEtC,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE;YAC3C,mBAAmB;YACnB,oBAAoB,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;YAEjD,IAAI,GAAG,CAAC,MAAM,KAAK,SAAS,EAAE,CAAC;gBAC7B,GAAG,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC;gBACnB,GAAG,CAAC,GAAG,EAAE,CAAC;gBACV,OAAO;YACT,CAAC;YAED,oCAAoC;YACpC,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;YACxD,IAAI,GAAG,CAAC,QAAQ,KAAK,gBAAgB,IAAI,GAAG,CAAC,QAAQ,KAAK,eAAe,EAAE,CAAC;gBAC1E,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;gBAC1C,GAAG,CAAC,SAAS,CAAC,uBAAuB,EAAE,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,CAAC;gBAChE,GAAG,CAAC,SAAS,CAAC,mBAAmB,EAAE,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;gBAC5E,IAAI,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC;oBACnB,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,qBAAqB,EAAE,CAAC,CAAC;oBACpF,OAAO;gBACT,CAAC;YACH,CAAC;YAED,eAAe;YACf,IAAI,MAAM,EAAE,CAAC;gBACX,MAAM,QAAQ,GAAI,GAAG,CAAC,OAAO,CAAC,WAAW,CAAY;oBACnD,GAAG,CAAC,OAAO,CAAC,aAAa,EAAE,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC;gBACpD,IAAI,QAAQ,KAAK,MAAM,EAAE,CAAC;oBACxB,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,4BAA4B,EAAE,CAAC,CAAC;oBACtF,OAAO;gBACT,CAAC;YACH,CAAC;YAED,IAAI,CAAC,aAAa,CAAC,GAAG,EAAE,GAAG,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBACzC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,GAAG,CAAC,CAAC;gBACrC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;oBAClB,KAAK,EAAE,uBAAuB;oBAC9B,OAAO,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC;iBAC1D,CAAC,CAAC;YACL,CAAC,CAAC,CAAC;QACL,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;YAC5B,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,+CAA+C,IAAI,EAAE,CAAC,CAAC;QAC1E,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI;QACF,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC;QACxB,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACrC,IAAI,CAAC;gBAAC,MAAM,CAAC,GAAG,EAAE,CAAC;YAAC,CAAC;YAAC,MAAM,CAAC,CAAC,YAAY,CAAC,CAAC;QAC9C,CAAC;QACD,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;QACxB,IAAI,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC;QACrB,IAAI,CAAC,MAAM,GAAG,IAAI,CAAC;QACnB,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,yBAAyB,CAAC,CAAC;IAC9C,CAAC;IAED,qDAAqD;IAC3C,WAAW;QACnB,OAAO,EAAE,CAAC;IACZ,CAAC;IAEO,KAAK,CAAC,aAAa,CAAC,GAAyB,EAAE,GAAwB;QAC7E,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,GAAG,EAAE,kBAAkB,CAAC,CAAC;QACxD,MAAM,QAAQ,GAAG,GAAG,CAAC,QAAQ,CAAC;QAC9B,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,IAAI,KAAK,CAAC;QACnC,MAAM,KAAK,GAAG,GAAG,CAAC,YAAY,CAAC;QAE/B,sBAAsB;QACtB,IAAI,QAAQ,KAAK,gBAAgB,EAAE,CAAC;YAClC,MAAM,IAAI,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,GAAG,GAAG,IAAI,GAAG,IAAI,CAAC,EAAE,CAAC;YACtK,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC;YACjD,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;YAC3C,OAAO;QACT,CAAC;QAED,mCAAmC;QACnC,IAAI,QAAQ,KAAK,eAAe,EAAE,CAAC;YACjC,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,IAAI,EAAE,CAAC;YACjD,MAAM,KAAK,GAAG,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,KAAK,CAAC,GAAG,KAAK,KAAK,CAAC;YACxD,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,CAAC;YAC5F,OAAO;QACT,CAAC;QAED,mBAAmB;QACnB,IAAI,QAAQ,KAAK,gBAAgB,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;YACtD,GAAG,CAAC,SAAS,CAAC,GAAG,EAAE;gBACjB,cAAc,EAAE,mBAAmB;gBACnC,eAAe,EAAE,UAAU;gBAC3B,YAAY,EAAE,YAAY;aAC3B,CAAC,CAAC;YACH,GAAG,CAAC,KAAK,CAAC,gCAAgC,CAAC,CAAC;YAC5C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;YACzB,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC;YACnD,OAAO;QACT,CAAC;QAED,6BAA6B;QAC7B,IAAI,QAAQ,KAAK,iBAAiB,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;YACvD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC;YAClD,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE;gBAClB,OAAO;gBACP,WAAW,EAAE,aAAa;gBAC1B,KAAK,EAAE,sEAAsE;aAC9E,CAAC,CAAC;YACH,OAAO;QACT,CAAC;QAED,uBAAuB;QACvB,IAAI,QAAQ,KAAK,aAAa,IAAI,MAAM,KAAK,MAAM,EAAE,CAAC;YACpD,MAAM,UAAU,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;YACxE,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;gBACrB,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;gBAC/E,OAAO;YACT,CAAC;YACD,IAAI,CAAC,UAAU,CAAC,IAAI,EAAE,CAAC;gBACrB,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,CAAC;gBAC7E,OAAO;YACT,CAAC;YAED,MAAM,MAAM,GAAG,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;YAE3C,oBAAoB;YACpB,IAAI,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC;gBAC1B,MAAM,OAAO,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,IAAgE,EAAE,EAAE;oBAC9F,IAAI,CAAC;wBACH,MAAM,SAAS,GAAG,cAAc,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;wBAC9C,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;wBAClE,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,CAAC;oBACjC,CAAC;oBAAC,OAAO,GAAG,EAAE,CAAC;wBACb,MAAM,IAAI,GAAG,GAAG,YAAY,eAAe,CAAC,CAAC,CAAC,kBAAkB,CAAC,CAAC,CAAC,OAAO,CAAC;wBAC3E,OAAO,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;oBACxF,CAAC;gBACH,CAAC,CAAC,CAAC;gBACH,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,OAAO,CAAC,CAAC;gBAC7B,OAAO;YACT,CAAC;YAED,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;gBACnB,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,wBAAwB,EAAE,CAAC,CAAC;gBACjF,OAAO;YACT,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,cAAc,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAChD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,EAAE,SAAS,CAAC,CAAC;gBACpE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;YAClC,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,MAAM,GAAG,GAAG,YAAY,eAAe,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;gBAC1D,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;YACtF,CAAC;YACD,OAAO;QACT,CAAC;QAED,iBAAiB;QACjB,IAAI,IAAI,GAAY,SAAS,CAAC;QAC9B,IAAI,MAAM,KAAK,MAAM,IAAI,MAAM,KAAK,KAAK,EAAE,CAAC;YAC1C,IAAI,CAAC;gBACH,MAAM,UAAU,GAAG,MAAM,iBAAiB,CAAC,GAAG,EAAE,IAAI,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;gBACxE,IAAI,UAAU,CAAC,KAAK,EAAE,CAAC;oBACrB,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,mBAAmB,EAAE,OAAO,EAAE,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;oBAC/E,OAAO;gBACT,CAAC;gBACD,IAAI,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;YAC5D,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,mBAAmB,EAAE,CAAC,CAAC;gBAC5E,OAAO;YACT,CAAC;QACH,CAAC;QAED,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;YAChC,IAAI,KAAK,CAAC,MAAM,KAAK,MAAM;gBAAE,SAAS;YACtC,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC;YAC5C,IAAI,CAAC,KAAK;gBAAE,SAAS;YAErB,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;gBACvD,MAAM,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;gBACnE,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,KAAK,MAAM,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,CAAC,CAAC;YAC5D,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,GAAG,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;gBAC7D,MAAM,MAAM,GAAG,GAAG,CAAC,UAAU,CAAC,gBAAgB,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,CAAC;gBAC5D,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,EAAE,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC;YACzC,CAAC;YACD,OAAO;QACT,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,OAAO,EAAE,gBAAgB,MAAM,IAAI,QAAQ,EAAE,EAAE,CAAC,CAAC;IAC7F,CAAC;IAES,IAAI,CAAC,GAAwB,EAAE,MAAc,EAAE,IAAa;QACpE,GAAG,CAAC,SAAS,CAAC,MAAM,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE,CAAC,CAAC;QAC9D,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAChC,CAAC;IAES,QAAQ,CAAC,GAAyB;QAC1C,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACrC,MAAM,MAAM,GAAa,EAAE,CAAC;YAC5B,GAAG,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAa,EAAE,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC;YACtD,GAAG,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC;YACrE,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;QAC1B,CAAC,CAAC,CAAC;IACL,CAAC;IAES,YAAY,CAAC,IAAa;QAClC,MAAM,GAAG,GAAG,SAAS,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,MAAM,CAAC;QAChD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACrC,IAAI,CAAC;gBACH,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;YACpB,CAAC;YAAC,MAAM,CAAC;gBACP,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;YACjC,CAAC;QACH,CAAC;IACH,CAAC;CACF"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
export interface CorrelatorEvent {
|
|
2
|
+
source: string;
|
|
3
|
+
event: string;
|
|
4
|
+
data: unknown;
|
|
5
|
+
timestamp: number;
|
|
6
|
+
}
|
|
7
|
+
export interface Correlation {
|
|
8
|
+
id: string;
|
|
9
|
+
sourceA: string;
|
|
10
|
+
eventA: string;
|
|
11
|
+
sourceB: string;
|
|
12
|
+
eventB: string;
|
|
13
|
+
type: string;
|
|
14
|
+
strength: number;
|
|
15
|
+
count: number;
|
|
16
|
+
lastSeen: number;
|
|
17
|
+
}
|
|
18
|
+
export interface EcosystemHealth {
|
|
19
|
+
score: number;
|
|
20
|
+
status: 'healthy' | 'degraded' | 'critical';
|
|
21
|
+
activeBrains: number;
|
|
22
|
+
totalEvents: number;
|
|
23
|
+
correlations: number;
|
|
24
|
+
recentErrors: number;
|
|
25
|
+
recentTradeLosses: number;
|
|
26
|
+
alerts: string[];
|
|
27
|
+
}
|
|
28
|
+
export interface CorrelatorConfig {
|
|
29
|
+
maxEvents?: number;
|
|
30
|
+
windowMs?: number;
|
|
31
|
+
decayFactor?: number;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* CrossBrainCorrelator — detects temporal correlations between events
|
|
35
|
+
* from different brains in the ecosystem.
|
|
36
|
+
*/
|
|
37
|
+
export declare class CrossBrainCorrelator {
|
|
38
|
+
private logger;
|
|
39
|
+
private events;
|
|
40
|
+
private correlations;
|
|
41
|
+
private brainLastSeen;
|
|
42
|
+
private maxEvents;
|
|
43
|
+
private windowMs;
|
|
44
|
+
private decayFactor;
|
|
45
|
+
constructor(config?: CorrelatorConfig);
|
|
46
|
+
/**
|
|
47
|
+
* Record an event from a brain. Adds to the circular buffer and
|
|
48
|
+
* runs correlation detection against recent events from OTHER brains.
|
|
49
|
+
*/
|
|
50
|
+
recordEvent(source: string, event: string, data: unknown): void;
|
|
51
|
+
/**
|
|
52
|
+
* Look at the last event added. Find events from other brains within
|
|
53
|
+
* the correlation window. For each match, create or update a Correlation.
|
|
54
|
+
*/
|
|
55
|
+
private detectCorrelations;
|
|
56
|
+
/**
|
|
57
|
+
* Classify the correlation type based on the two events.
|
|
58
|
+
* Order-independent: checks both directions.
|
|
59
|
+
*/
|
|
60
|
+
private classifyCorrelation;
|
|
61
|
+
/**
|
|
62
|
+
* Build a stable correlation ID by sorting the two event descriptors
|
|
63
|
+
* alphabetically. This ensures the same pair always produces the same ID
|
|
64
|
+
* regardless of arrival order.
|
|
65
|
+
*/
|
|
66
|
+
private buildCorrelationId;
|
|
67
|
+
/**
|
|
68
|
+
* Return correlations sorted by strength (descending).
|
|
69
|
+
* Optionally filter by minimum strength.
|
|
70
|
+
*/
|
|
71
|
+
getCorrelations(minStrength?: number): Correlation[];
|
|
72
|
+
/**
|
|
73
|
+
* Return the most recent events from the buffer.
|
|
74
|
+
*/
|
|
75
|
+
getTimeline(limit?: number): CorrelatorEvent[];
|
|
76
|
+
/**
|
|
77
|
+
* Compute ecosystem health based on recent events and correlations.
|
|
78
|
+
*/
|
|
79
|
+
getHealth(): EcosystemHealth;
|
|
80
|
+
/**
|
|
81
|
+
* Return brain names that have sent an event in the last 60 seconds.
|
|
82
|
+
*/
|
|
83
|
+
getActiveBrains(): string[];
|
|
84
|
+
/**
|
|
85
|
+
* Return the total number of events currently in the buffer.
|
|
86
|
+
*/
|
|
87
|
+
getEventCount(): number;
|
|
88
|
+
/**
|
|
89
|
+
* Reset all state: events, correlations, and brain tracking.
|
|
90
|
+
*/
|
|
91
|
+
clear(): void;
|
|
92
|
+
}
|