@timmeck/brain-core 2.1.0 → 2.3.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 +108 -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/backup/service.d.ts +48 -0
- package/dist/backup/service.js +153 -0
- package/dist/backup/service.js.map +1 -0
- package/dist/export/service.d.ts +42 -0
- package/dist/export/service.js +118 -0
- package/dist/export/service.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +9 -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/dist/webhooks/service.d.ts +65 -0
- package/dist/webhooks/service.js +157 -0
- package/dist/webhooks/service.js.map +1 -0
- package/package.json +8 -2
|
@@ -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,108 @@
|
|
|
1
|
+
// ── Rate Limiter ────────────────────────────────────────
|
|
2
|
+
export class RateLimiter {
|
|
3
|
+
store = new Map();
|
|
4
|
+
windowMs;
|
|
5
|
+
maxRequests;
|
|
6
|
+
keyExtractor;
|
|
7
|
+
cleanupTimer;
|
|
8
|
+
constructor(config) {
|
|
9
|
+
this.windowMs = config?.windowMs ?? 60_000;
|
|
10
|
+
this.maxRequests = config?.maxRequests ?? 100;
|
|
11
|
+
this.keyExtractor = config?.keyExtractor ?? ((req) => req.socket.remoteAddress ?? 'unknown');
|
|
12
|
+
// Cleanup expired entries every minute
|
|
13
|
+
this.cleanupTimer = setInterval(() => this.cleanup(), 60_000);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Check if a request is allowed. Returns { allowed, remaining, resetAt }.
|
|
17
|
+
*/
|
|
18
|
+
check(req) {
|
|
19
|
+
const key = this.keyExtractor(req);
|
|
20
|
+
const now = Date.now();
|
|
21
|
+
const entry = this.store.get(key);
|
|
22
|
+
if (!entry || now >= entry.resetAt) {
|
|
23
|
+
this.store.set(key, { count: 1, resetAt: now + this.windowMs });
|
|
24
|
+
return { allowed: true, remaining: this.maxRequests - 1, resetAt: now + this.windowMs };
|
|
25
|
+
}
|
|
26
|
+
entry.count++;
|
|
27
|
+
if (entry.count > this.maxRequests) {
|
|
28
|
+
return { allowed: false, remaining: 0, resetAt: entry.resetAt };
|
|
29
|
+
}
|
|
30
|
+
return { allowed: true, remaining: this.maxRequests - entry.count, resetAt: entry.resetAt };
|
|
31
|
+
}
|
|
32
|
+
/** Reset a specific key (for testing) */
|
|
33
|
+
reset(key) {
|
|
34
|
+
this.store.delete(key);
|
|
35
|
+
}
|
|
36
|
+
/** Clear all entries */
|
|
37
|
+
clear() {
|
|
38
|
+
this.store.clear();
|
|
39
|
+
}
|
|
40
|
+
/** Stop cleanup timer */
|
|
41
|
+
stop() {
|
|
42
|
+
clearInterval(this.cleanupTimer);
|
|
43
|
+
}
|
|
44
|
+
cleanup() {
|
|
45
|
+
const now = Date.now();
|
|
46
|
+
for (const [key, entry] of this.store) {
|
|
47
|
+
if (now >= entry.resetAt) {
|
|
48
|
+
this.store.delete(key);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Read request body with size limit enforcement.
|
|
55
|
+
* Returns the body string or null if the limit is exceeded.
|
|
56
|
+
*/
|
|
57
|
+
export function readBodyWithLimit(req, config) {
|
|
58
|
+
const maxBytes = config?.maxBodyBytes ?? 102_400;
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
const chunks = [];
|
|
61
|
+
let totalBytes = 0;
|
|
62
|
+
let aborted = false;
|
|
63
|
+
req.on('data', (chunk) => {
|
|
64
|
+
if (aborted)
|
|
65
|
+
return;
|
|
66
|
+
totalBytes += chunk.length;
|
|
67
|
+
if (totalBytes > maxBytes) {
|
|
68
|
+
aborted = true;
|
|
69
|
+
req.destroy();
|
|
70
|
+
resolve({ error: `Request body exceeds limit of ${maxBytes} bytes` });
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
chunks.push(chunk);
|
|
74
|
+
});
|
|
75
|
+
req.on('end', () => {
|
|
76
|
+
if (aborted)
|
|
77
|
+
return;
|
|
78
|
+
resolve({ body: Buffer.concat(chunks).toString('utf8') });
|
|
79
|
+
});
|
|
80
|
+
req.on('error', () => {
|
|
81
|
+
if (aborted)
|
|
82
|
+
return;
|
|
83
|
+
resolve({ error: 'Request read error' });
|
|
84
|
+
});
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Apply security headers to an HTTP response.
|
|
89
|
+
*/
|
|
90
|
+
export function applySecurityHeaders(res, config) {
|
|
91
|
+
// CORS
|
|
92
|
+
const origins = config?.cors?.origins ?? ['*'];
|
|
93
|
+
const methods = config?.cors?.methods ?? ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'];
|
|
94
|
+
const headers = config?.cors?.headers ?? ['Content-Type', 'Authorization', 'X-API-Key'];
|
|
95
|
+
res.setHeader('Access-Control-Allow-Origin', origins.join(', '));
|
|
96
|
+
res.setHeader('Access-Control-Allow-Methods', methods.join(', '));
|
|
97
|
+
res.setHeader('Access-Control-Allow-Headers', headers.join(', '));
|
|
98
|
+
// Security headers
|
|
99
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
100
|
+
res.setHeader('X-Frame-Options', 'DENY');
|
|
101
|
+
res.setHeader('X-XSS-Protection', '1; mode=block');
|
|
102
|
+
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
|
|
103
|
+
// HSTS (only when explicitly enabled — requires HTTPS)
|
|
104
|
+
if (config?.hsts) {
|
|
105
|
+
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/api/middleware.ts"],"names":[],"mappings":"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"}
|
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":"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"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
export interface BackupConfig {
|
|
3
|
+
backupDir?: string;
|
|
4
|
+
maxBackups?: number;
|
|
5
|
+
compress?: boolean;
|
|
6
|
+
}
|
|
7
|
+
export interface BackupRecord {
|
|
8
|
+
filename: string;
|
|
9
|
+
path: string;
|
|
10
|
+
size: number;
|
|
11
|
+
created: string;
|
|
12
|
+
}
|
|
13
|
+
export interface RestoreResult {
|
|
14
|
+
success: boolean;
|
|
15
|
+
filename: string;
|
|
16
|
+
integrityOk: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare class BackupService {
|
|
19
|
+
private db;
|
|
20
|
+
private dbPath;
|
|
21
|
+
private logger;
|
|
22
|
+
private backupDir;
|
|
23
|
+
private maxBackups;
|
|
24
|
+
constructor(db: Database.Database, dbPath: string, config?: BackupConfig);
|
|
25
|
+
/** Create a backup of the current database. */
|
|
26
|
+
create(label?: string): BackupRecord;
|
|
27
|
+
/** List all available backups (newest first). */
|
|
28
|
+
list(): BackupRecord[];
|
|
29
|
+
/**
|
|
30
|
+
* Restore a database from a backup.
|
|
31
|
+
* CAUTION: This replaces the current database. The caller must close and reopen the DB connection.
|
|
32
|
+
*/
|
|
33
|
+
restore(filename: string): RestoreResult;
|
|
34
|
+
/** Check integrity of a database file. */
|
|
35
|
+
checkIntegrity(dbFilePath?: string): boolean;
|
|
36
|
+
/** Get current database size info. */
|
|
37
|
+
getInfo(): {
|
|
38
|
+
dbSize: number;
|
|
39
|
+
walSize: number;
|
|
40
|
+
backupCount: number;
|
|
41
|
+
backupTotalSize: number;
|
|
42
|
+
};
|
|
43
|
+
/** Delete a specific backup. */
|
|
44
|
+
delete(filename: string): boolean;
|
|
45
|
+
/** Auto-cleanup oldest backups beyond maxBackups. */
|
|
46
|
+
private autoCleanup;
|
|
47
|
+
private formatSize;
|
|
48
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { createRequire } from 'node:module';
|
|
4
|
+
import { getLogger } from '../utils/logger.js';
|
|
5
|
+
const require = createRequire(import.meta.url);
|
|
6
|
+
// ── Service ─────────────────────────────────────────────
|
|
7
|
+
export class BackupService {
|
|
8
|
+
db;
|
|
9
|
+
dbPath;
|
|
10
|
+
logger = getLogger();
|
|
11
|
+
backupDir;
|
|
12
|
+
maxBackups;
|
|
13
|
+
constructor(db, dbPath, config) {
|
|
14
|
+
this.db = db;
|
|
15
|
+
this.dbPath = dbPath;
|
|
16
|
+
this.backupDir = config?.backupDir ?? path.join(path.dirname(dbPath), 'backups');
|
|
17
|
+
this.maxBackups = config?.maxBackups ?? 10;
|
|
18
|
+
fs.mkdirSync(this.backupDir, { recursive: true });
|
|
19
|
+
}
|
|
20
|
+
/** Create a backup of the current database. */
|
|
21
|
+
create(label) {
|
|
22
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
|
|
23
|
+
const suffix = label ? `-${label.replace(/[^a-zA-Z0-9-_]/g, '')}` : '';
|
|
24
|
+
const filename = `backup-${timestamp}${suffix}.db`;
|
|
25
|
+
const backupPath = path.join(this.backupDir, filename);
|
|
26
|
+
// Checkpoint WAL before copying for a consistent snapshot
|
|
27
|
+
this.db.pragma('wal_checkpoint(TRUNCATE)');
|
|
28
|
+
// Copy DB file synchronously
|
|
29
|
+
fs.copyFileSync(this.dbPath, backupPath);
|
|
30
|
+
// Verify integrity
|
|
31
|
+
const integrityOk = this.checkIntegrity(backupPath);
|
|
32
|
+
if (!integrityOk) {
|
|
33
|
+
this.logger.warn(`Backup integrity check failed: ${filename}`);
|
|
34
|
+
fs.unlinkSync(backupPath);
|
|
35
|
+
throw new Error('Backup integrity check failed');
|
|
36
|
+
}
|
|
37
|
+
this.logger.info(`Backup created: ${filename} (${this.formatSize(fs.statSync(backupPath).size)})`);
|
|
38
|
+
// Auto-cleanup old backups
|
|
39
|
+
this.autoCleanup();
|
|
40
|
+
const stat = fs.statSync(backupPath);
|
|
41
|
+
return {
|
|
42
|
+
filename,
|
|
43
|
+
path: backupPath,
|
|
44
|
+
size: stat.size,
|
|
45
|
+
created: stat.mtime.toISOString(),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** List all available backups (newest first). */
|
|
49
|
+
list() {
|
|
50
|
+
if (!fs.existsSync(this.backupDir))
|
|
51
|
+
return [];
|
|
52
|
+
const files = fs.readdirSync(this.backupDir)
|
|
53
|
+
.filter(f => f.startsWith('backup-') && f.endsWith('.db'))
|
|
54
|
+
.sort()
|
|
55
|
+
.reverse();
|
|
56
|
+
return files.map(filename => {
|
|
57
|
+
const filePath = path.join(this.backupDir, filename);
|
|
58
|
+
const stat = fs.statSync(filePath);
|
|
59
|
+
return {
|
|
60
|
+
filename,
|
|
61
|
+
path: filePath,
|
|
62
|
+
size: stat.size,
|
|
63
|
+
created: stat.mtime.toISOString(),
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Restore a database from a backup.
|
|
69
|
+
* CAUTION: This replaces the current database. The caller must close and reopen the DB connection.
|
|
70
|
+
*/
|
|
71
|
+
restore(filename) {
|
|
72
|
+
const backupPath = path.join(this.backupDir, filename);
|
|
73
|
+
if (!fs.existsSync(backupPath)) {
|
|
74
|
+
throw new Error(`Backup not found: ${filename}`);
|
|
75
|
+
}
|
|
76
|
+
// Verify backup integrity before restore
|
|
77
|
+
const integrityOk = this.checkIntegrity(backupPath);
|
|
78
|
+
if (!integrityOk) {
|
|
79
|
+
throw new Error(`Backup integrity check failed: ${filename}`);
|
|
80
|
+
}
|
|
81
|
+
// Create a safety backup of current DB before overwriting
|
|
82
|
+
const safetyName = `pre-restore-${new Date().toISOString().replace(/[:.]/g, '-')}.db`;
|
|
83
|
+
const safetyPath = path.join(this.backupDir, safetyName);
|
|
84
|
+
this.db.pragma('wal_checkpoint(TRUNCATE)');
|
|
85
|
+
fs.copyFileSync(this.dbPath, safetyPath);
|
|
86
|
+
this.logger.info(`Safety backup created: ${safetyName}`);
|
|
87
|
+
// Close current DB, copy backup over, caller must reopen
|
|
88
|
+
this.db.close();
|
|
89
|
+
fs.copyFileSync(backupPath, this.dbPath);
|
|
90
|
+
// Clean up WAL/SHM files if they exist
|
|
91
|
+
try {
|
|
92
|
+
fs.unlinkSync(this.dbPath + '-wal');
|
|
93
|
+
}
|
|
94
|
+
catch { /* may not exist */ }
|
|
95
|
+
try {
|
|
96
|
+
fs.unlinkSync(this.dbPath + '-shm');
|
|
97
|
+
}
|
|
98
|
+
catch { /* may not exist */ }
|
|
99
|
+
this.logger.info(`Database restored from: ${filename}`);
|
|
100
|
+
return { success: true, filename, integrityOk };
|
|
101
|
+
}
|
|
102
|
+
/** Check integrity of a database file. */
|
|
103
|
+
checkIntegrity(dbFilePath) {
|
|
104
|
+
const filePath = dbFilePath ?? this.dbPath;
|
|
105
|
+
try {
|
|
106
|
+
const BetterSqlite3 = require('better-sqlite3');
|
|
107
|
+
const testDb = new BetterSqlite3(filePath, { readonly: true });
|
|
108
|
+
const result = testDb.pragma('integrity_check');
|
|
109
|
+
testDb.close();
|
|
110
|
+
return result.length === 1 && result[0].integrity_check === 'ok';
|
|
111
|
+
}
|
|
112
|
+
catch {
|
|
113
|
+
return false;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
/** Get current database size info. */
|
|
117
|
+
getInfo() {
|
|
118
|
+
const dbSize = fs.existsSync(this.dbPath) ? fs.statSync(this.dbPath).size : 0;
|
|
119
|
+
const walPath = this.dbPath + '-wal';
|
|
120
|
+
const walSize = fs.existsSync(walPath) ? fs.statSync(walPath).size : 0;
|
|
121
|
+
const backups = this.list();
|
|
122
|
+
const backupTotalSize = backups.reduce((sum, b) => sum + b.size, 0);
|
|
123
|
+
return { dbSize, walSize, backupCount: backups.length, backupTotalSize };
|
|
124
|
+
}
|
|
125
|
+
/** Delete a specific backup. */
|
|
126
|
+
delete(filename) {
|
|
127
|
+
const filePath = path.join(this.backupDir, filename);
|
|
128
|
+
if (!fs.existsSync(filePath))
|
|
129
|
+
return false;
|
|
130
|
+
fs.unlinkSync(filePath);
|
|
131
|
+
this.logger.info(`Backup deleted: ${filename}`);
|
|
132
|
+
return true;
|
|
133
|
+
}
|
|
134
|
+
/** Auto-cleanup oldest backups beyond maxBackups. */
|
|
135
|
+
autoCleanup() {
|
|
136
|
+
const backups = this.list(); // sorted newest first
|
|
137
|
+
if (backups.length <= this.maxBackups)
|
|
138
|
+
return;
|
|
139
|
+
const toDelete = backups.slice(this.maxBackups);
|
|
140
|
+
for (const backup of toDelete) {
|
|
141
|
+
this.delete(backup.filename);
|
|
142
|
+
}
|
|
143
|
+
this.logger.info(`Auto-cleaned ${toDelete.length} old backup(s)`);
|
|
144
|
+
}
|
|
145
|
+
formatSize(bytes) {
|
|
146
|
+
if (bytes < 1024)
|
|
147
|
+
return `${bytes}B`;
|
|
148
|
+
if (bytes < 1024 * 1024)
|
|
149
|
+
return `${(bytes / 1024).toFixed(1)}KB`;
|
|
150
|
+
return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/backup/service.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
export interface ExportOptions {
|
|
3
|
+
table: string;
|
|
4
|
+
format: 'json' | 'csv';
|
|
5
|
+
columns?: string[];
|
|
6
|
+
where?: string;
|
|
7
|
+
orderBy?: string;
|
|
8
|
+
limit?: number;
|
|
9
|
+
dateColumn?: string;
|
|
10
|
+
dateFrom?: string;
|
|
11
|
+
dateTo?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface ExportResult {
|
|
14
|
+
table: string;
|
|
15
|
+
format: 'json' | 'csv';
|
|
16
|
+
rowCount: number;
|
|
17
|
+
data: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class ExportService {
|
|
20
|
+
private db;
|
|
21
|
+
private logger;
|
|
22
|
+
constructor(db: Database.Database);
|
|
23
|
+
/** Get list of all tables in the database. */
|
|
24
|
+
listTables(): string[];
|
|
25
|
+
/** Get column info for a table. */
|
|
26
|
+
getColumns(table: string): {
|
|
27
|
+
name: string;
|
|
28
|
+
type: string;
|
|
29
|
+
}[];
|
|
30
|
+
/** Export data from a table. */
|
|
31
|
+
export(options: ExportOptions): ExportResult;
|
|
32
|
+
/** Export multiple tables at once (JSON only). */
|
|
33
|
+
exportAll(tables?: string[], format?: 'json' | 'csv'): Record<string, ExportResult>;
|
|
34
|
+
/** Get row counts for all tables. */
|
|
35
|
+
getStats(): Record<string, number>;
|
|
36
|
+
/** Convert array of objects to CSV string. */
|
|
37
|
+
private toCsv;
|
|
38
|
+
/** Validate table name to prevent SQL injection. */
|
|
39
|
+
private validateTableName;
|
|
40
|
+
/** Validate column name. */
|
|
41
|
+
private validateColumnName;
|
|
42
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { getLogger } from '../utils/logger.js';
|
|
2
|
+
// ── Service ─────────────────────────────────────────────
|
|
3
|
+
export class ExportService {
|
|
4
|
+
db;
|
|
5
|
+
logger = getLogger();
|
|
6
|
+
constructor(db) {
|
|
7
|
+
this.db = db;
|
|
8
|
+
}
|
|
9
|
+
/** Get list of all tables in the database. */
|
|
10
|
+
listTables() {
|
|
11
|
+
const rows = this.db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name NOT LIKE 'sqlite_%' ORDER BY name`).all();
|
|
12
|
+
return rows.map(r => r.name);
|
|
13
|
+
}
|
|
14
|
+
/** Get column info for a table. */
|
|
15
|
+
getColumns(table) {
|
|
16
|
+
this.validateTableName(table);
|
|
17
|
+
const rows = this.db.prepare(`PRAGMA table_info("${table}")`).all();
|
|
18
|
+
return rows.map(r => ({ name: r.name, type: r.type }));
|
|
19
|
+
}
|
|
20
|
+
/** Export data from a table. */
|
|
21
|
+
export(options) {
|
|
22
|
+
this.validateTableName(options.table);
|
|
23
|
+
const columns = options.columns?.length
|
|
24
|
+
? options.columns.map(c => `"${c.replace(/"/g, '""')}"`).join(', ')
|
|
25
|
+
: '*';
|
|
26
|
+
const conditions = [];
|
|
27
|
+
const params = [];
|
|
28
|
+
if (options.where) {
|
|
29
|
+
conditions.push(`(${options.where})`);
|
|
30
|
+
}
|
|
31
|
+
if (options.dateColumn && options.dateFrom) {
|
|
32
|
+
this.validateColumnName(options.dateColumn);
|
|
33
|
+
conditions.push(`"${options.dateColumn}" >= ?`);
|
|
34
|
+
params.push(options.dateFrom);
|
|
35
|
+
}
|
|
36
|
+
if (options.dateColumn && options.dateTo) {
|
|
37
|
+
this.validateColumnName(options.dateColumn);
|
|
38
|
+
conditions.push(`"${options.dateColumn}" <= ?`);
|
|
39
|
+
params.push(options.dateTo);
|
|
40
|
+
}
|
|
41
|
+
let sql = `SELECT ${columns} FROM "${options.table}"`;
|
|
42
|
+
if (conditions.length > 0) {
|
|
43
|
+
sql += ` WHERE ${conditions.join(' AND ')}`;
|
|
44
|
+
}
|
|
45
|
+
if (options.orderBy) {
|
|
46
|
+
sql += ` ORDER BY ${options.orderBy}`;
|
|
47
|
+
}
|
|
48
|
+
if (options.limit) {
|
|
49
|
+
sql += ` LIMIT ?`;
|
|
50
|
+
params.push(options.limit);
|
|
51
|
+
}
|
|
52
|
+
const rows = this.db.prepare(sql).all(...params);
|
|
53
|
+
const data = options.format === 'csv'
|
|
54
|
+
? this.toCsv(rows)
|
|
55
|
+
: JSON.stringify(rows, null, 2);
|
|
56
|
+
this.logger.info(`Exported ${rows.length} rows from ${options.table} as ${options.format}`);
|
|
57
|
+
return {
|
|
58
|
+
table: options.table,
|
|
59
|
+
format: options.format,
|
|
60
|
+
rowCount: rows.length,
|
|
61
|
+
data,
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
/** Export multiple tables at once (JSON only). */
|
|
65
|
+
exportAll(tables, format = 'json') {
|
|
66
|
+
const tableNames = tables ?? this.listTables();
|
|
67
|
+
const results = {};
|
|
68
|
+
for (const table of tableNames) {
|
|
69
|
+
results[table] = this.export({ table, format });
|
|
70
|
+
}
|
|
71
|
+
return results;
|
|
72
|
+
}
|
|
73
|
+
/** Get row counts for all tables. */
|
|
74
|
+
getStats() {
|
|
75
|
+
const tables = this.listTables();
|
|
76
|
+
const stats = {};
|
|
77
|
+
for (const table of tables) {
|
|
78
|
+
const row = this.db.prepare(`SELECT COUNT(*) as count FROM "${table}"`).get();
|
|
79
|
+
stats[table] = row.count;
|
|
80
|
+
}
|
|
81
|
+
return stats;
|
|
82
|
+
}
|
|
83
|
+
/** Convert array of objects to CSV string. */
|
|
84
|
+
toCsv(rows) {
|
|
85
|
+
if (rows.length === 0)
|
|
86
|
+
return '';
|
|
87
|
+
const headers = Object.keys(rows[0]);
|
|
88
|
+
const lines = [headers.join(',')];
|
|
89
|
+
for (const row of rows) {
|
|
90
|
+
const values = headers.map(h => {
|
|
91
|
+
const val = row[h];
|
|
92
|
+
if (val == null)
|
|
93
|
+
return '';
|
|
94
|
+
const str = String(val);
|
|
95
|
+
// Escape CSV: quote if contains comma, quote, or newline
|
|
96
|
+
if (str.includes(',') || str.includes('"') || str.includes('\n')) {
|
|
97
|
+
return `"${str.replace(/"/g, '""')}"`;
|
|
98
|
+
}
|
|
99
|
+
return str;
|
|
100
|
+
});
|
|
101
|
+
lines.push(values.join(','));
|
|
102
|
+
}
|
|
103
|
+
return lines.join('\n');
|
|
104
|
+
}
|
|
105
|
+
/** Validate table name to prevent SQL injection. */
|
|
106
|
+
validateTableName(name) {
|
|
107
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
108
|
+
throw new Error(`Invalid table name: ${name}`);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** Validate column name. */
|
|
112
|
+
validateColumnName(name) {
|
|
113
|
+
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) {
|
|
114
|
+
throw new Error(`Invalid column name: ${name}`);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/export/service.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
CHANGED
|
@@ -9,6 +9,9 @@ export { encodeMessage, MessageDecoder } from './ipc/protocol.js';
|
|
|
9
9
|
export { IpcServer } from './ipc/server.js';
|
|
10
10
|
export type { IpcRouter } from './ipc/server.js';
|
|
11
11
|
export { IpcClient } from './ipc/client.js';
|
|
12
|
+
export { validateParams, withValidation } from './ipc/validation.js';
|
|
13
|
+
export type { ValidationOptions } from './ipc/validation.js';
|
|
14
|
+
export { IpcError, ValidationError, NotFoundError, TimeoutError, ServiceUnavailableError } from './ipc/errors.js';
|
|
12
15
|
export { startMcpServer } from './mcp/server.js';
|
|
13
16
|
export type { McpServerOptions } from './mcp/server.js';
|
|
14
17
|
export { McpHttpServer } from './mcp/http-server.js';
|
|
@@ -16,6 +19,8 @@ export type { McpHttpServerOptions } from './mcp/http-server.js';
|
|
|
16
19
|
export { c, baseIcons, header, keyValue, statusBadge, progressBar, divider, table, stripAnsi } from './cli/colors.js';
|
|
17
20
|
export { BaseApiServer } from './api/server.js';
|
|
18
21
|
export type { ApiServerOptions, RouteDefinition } from './api/server.js';
|
|
22
|
+
export { RateLimiter, readBodyWithLimit, applySecurityHeaders } from './api/middleware.js';
|
|
23
|
+
export type { RateLimitConfig, SizeLimitConfig, SecurityHeadersConfig } from './api/middleware.js';
|
|
19
24
|
export { wilsonScore } from './math/wilson-score.js';
|
|
20
25
|
export { timeDecayFactor } from './math/time-decay.js';
|
|
21
26
|
export { deepMerge, loadConfigFile } from './config/loader.js';
|
|
@@ -48,3 +53,9 @@ export { CrossBrainCorrelator } from './cross-brain/correlator.js';
|
|
|
48
53
|
export type { CorrelatorEvent, Correlation, EcosystemHealth, CorrelatorConfig } from './cross-brain/correlator.js';
|
|
49
54
|
export { EcosystemService } from './ecosystem/service.js';
|
|
50
55
|
export type { BrainStatus, EcosystemStatus, AggregatedAnalytics } from './ecosystem/service.js';
|
|
56
|
+
export { WebhookService, runWebhookMigration } from './webhooks/service.js';
|
|
57
|
+
export type { WebhookConfig, WebhookRecord, DeliveryRecord, WebhookDeliveryResult } from './webhooks/service.js';
|
|
58
|
+
export { ExportService } from './export/service.js';
|
|
59
|
+
export type { ExportOptions, ExportResult } from './export/service.js';
|
|
60
|
+
export { BackupService } from './backup/service.js';
|
|
61
|
+
export type { BackupConfig, BackupRecord, RestoreResult } from './backup/service.js';
|
package/dist/index.js
CHANGED
|
@@ -9,6 +9,8 @@ export { createConnection } from './db/connection.js';
|
|
|
9
9
|
export { encodeMessage, MessageDecoder } from './ipc/protocol.js';
|
|
10
10
|
export { IpcServer } from './ipc/server.js';
|
|
11
11
|
export { IpcClient } from './ipc/client.js';
|
|
12
|
+
export { validateParams, withValidation } from './ipc/validation.js';
|
|
13
|
+
export { IpcError, ValidationError, NotFoundError, TimeoutError, ServiceUnavailableError } from './ipc/errors.js';
|
|
12
14
|
// ── MCP ────────────────────────────────────────────────────
|
|
13
15
|
export { startMcpServer } from './mcp/server.js';
|
|
14
16
|
export { McpHttpServer } from './mcp/http-server.js';
|
|
@@ -16,6 +18,7 @@ export { McpHttpServer } from './mcp/http-server.js';
|
|
|
16
18
|
export { c, baseIcons, header, keyValue, statusBadge, progressBar, divider, table, stripAnsi } from './cli/colors.js';
|
|
17
19
|
// ── API ────────────────────────────────────────────────────
|
|
18
20
|
export { BaseApiServer } from './api/server.js';
|
|
21
|
+
export { RateLimiter, readBodyWithLimit, applySecurityHeaders } from './api/middleware.js';
|
|
19
22
|
// ── Math ───────────────────────────────────────────────────
|
|
20
23
|
export { wilsonScore } from './math/wilson-score.js';
|
|
21
24
|
export { timeDecayFactor } from './math/time-decay.js';
|
|
@@ -42,4 +45,10 @@ export { CrossBrainSubscriptionManager } from './cross-brain/subscription.js';
|
|
|
42
45
|
export { CrossBrainCorrelator } from './cross-brain/correlator.js';
|
|
43
46
|
// ── Ecosystem ──────────────────────────────────────────────
|
|
44
47
|
export { EcosystemService } from './ecosystem/service.js';
|
|
48
|
+
// ── Webhooks ──────────────────────────────────────────────
|
|
49
|
+
export { WebhookService, runWebhookMigration } from './webhooks/service.js';
|
|
50
|
+
// ── Export ────────────────────────────────────────────────
|
|
51
|
+
export { ExportService } from './export/service.js';
|
|
52
|
+
// ── Backup ────────────────────────────────────────────────
|
|
53
|
+
export { BackupService } from './backup/service.js';
|
|
45
54
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,8DAA8D;AAC9D,OAAO,EAAE,MAAM,EAAE,MAAM,iBAAiB,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEzE,OAAO,EAAE,aAAa,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAElD,8DAA8D;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,oBAAoB,CAAC;AAEtD,8DAA8D;AAC9D,OAAO,EAAE,aAAa,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAClE,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAE5C,OAAO,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error for IPC operations.
|
|
3
|
+
*/
|
|
4
|
+
export declare class IpcError extends Error {
|
|
5
|
+
readonly code: string;
|
|
6
|
+
readonly statusCode: number;
|
|
7
|
+
constructor(message: string, code: string, statusCode?: number);
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Thrown when IPC params fail validation.
|
|
11
|
+
*/
|
|
12
|
+
export declare class ValidationError extends IpcError {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Thrown when a requested method or resource is not found.
|
|
17
|
+
*/
|
|
18
|
+
export declare class NotFoundError extends IpcError {
|
|
19
|
+
constructor(message: string);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Thrown when an IPC operation times out.
|
|
23
|
+
*/
|
|
24
|
+
export declare class TimeoutError extends IpcError {
|
|
25
|
+
constructor(message?: string);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Thrown when a required service is not available.
|
|
29
|
+
*/
|
|
30
|
+
export declare class ServiceUnavailableError extends IpcError {
|
|
31
|
+
constructor(message?: string);
|
|
32
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Base error for IPC operations.
|
|
3
|
+
*/
|
|
4
|
+
export class IpcError extends Error {
|
|
5
|
+
code;
|
|
6
|
+
statusCode;
|
|
7
|
+
constructor(message, code, statusCode = 400) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'IpcError';
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.statusCode = statusCode;
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Thrown when IPC params fail validation.
|
|
16
|
+
*/
|
|
17
|
+
export class ValidationError extends IpcError {
|
|
18
|
+
constructor(message) {
|
|
19
|
+
super(message, 'VALIDATION_ERROR', 400);
|
|
20
|
+
this.name = 'ValidationError';
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when a requested method or resource is not found.
|
|
25
|
+
*/
|
|
26
|
+
export class NotFoundError extends IpcError {
|
|
27
|
+
constructor(message) {
|
|
28
|
+
super(message, 'NOT_FOUND', 404);
|
|
29
|
+
this.name = 'NotFoundError';
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Thrown when an IPC operation times out.
|
|
34
|
+
*/
|
|
35
|
+
export class TimeoutError extends IpcError {
|
|
36
|
+
constructor(message = 'Operation timed out') {
|
|
37
|
+
super(message, 'TIMEOUT', 408);
|
|
38
|
+
this.name = 'TimeoutError';
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Thrown when a required service is not available.
|
|
43
|
+
*/
|
|
44
|
+
export class ServiceUnavailableError extends IpcError {
|
|
45
|
+
constructor(message = 'Service unavailable') {
|
|
46
|
+
super(message, 'SERVICE_UNAVAILABLE', 503);
|
|
47
|
+
this.name = 'ServiceUnavailableError';
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/ipc/errors.ts"],"names":[],"mappings":"AAAA;;GAEG;AACH,MAAM,OAAO,QAAS,SAAQ,KAAK;IACxB,IAAI,CAAS;IACb,UAAU,CAAS;IAE5B,YAAY,OAAe,EAAE,IAAY,EAAE,aAAqB,GAAG;QACjE,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,UAAU,CAAC;QACvB,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,eAAgB,SAAQ,QAAQ;IAC3C,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,kBAAkB,EAAE,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,aAAc,SAAQ,QAAQ;IACzC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,CAAC,CAAC;QACjC,IAAI,CAAC,IAAI,GAAG,eAAe,CAAC;IAC9B,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,YAAa,SAAQ,QAAQ;IACxC,YAAY,UAAkB,qBAAqB;QACjD,KAAK,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,cAAc,CAAC;IAC7B,CAAC;CACF;AAED;;GAEG;AACH,MAAM,OAAO,uBAAwB,SAAQ,QAAQ;IACnD,YAAY,UAAkB,qBAAqB;QACjD,KAAK,CAAC,OAAO,EAAE,qBAAqB,EAAE,GAAG,CAAC,CAAC;QAC3C,IAAI,CAAC,IAAI,GAAG,yBAAyB,CAAC;IACxC,CAAC;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export interface ValidationOptions {
|
|
2
|
+
maxStringLength?: number;
|
|
3
|
+
maxArrayLength?: number;
|
|
4
|
+
maxDepth?: number;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Validate IPC method params. Ensures params is a plain object,
|
|
8
|
+
* string fields don't exceed length limits, arrays aren't too large,
|
|
9
|
+
* and nesting isn't too deep. Throws ValidationError on failure.
|
|
10
|
+
*/
|
|
11
|
+
export declare function validateParams(params: unknown, options?: ValidationOptions): Record<string, unknown>;
|
|
12
|
+
/**
|
|
13
|
+
* Create a validated IPC handler wrapper. Validates params before passing to the handler.
|
|
14
|
+
*/
|
|
15
|
+
export declare function withValidation<T>(handler: (params: Record<string, unknown>) => T, options?: ValidationOptions): (params: unknown) => T;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { ValidationError } from './errors.js';
|
|
2
|
+
const MAX_STRING_LENGTH = 10_240; // 10KB per string field
|
|
3
|
+
const MAX_ARRAY_LENGTH = 1000;
|
|
4
|
+
const MAX_DEPTH = 10;
|
|
5
|
+
/**
|
|
6
|
+
* Validate IPC method params. Ensures params is a plain object,
|
|
7
|
+
* string fields don't exceed length limits, arrays aren't too large,
|
|
8
|
+
* and nesting isn't too deep. Throws ValidationError on failure.
|
|
9
|
+
*/
|
|
10
|
+
export function validateParams(params, options) {
|
|
11
|
+
const maxStr = options?.maxStringLength ?? MAX_STRING_LENGTH;
|
|
12
|
+
const maxArr = options?.maxArrayLength ?? MAX_ARRAY_LENGTH;
|
|
13
|
+
const maxDep = options?.maxDepth ?? MAX_DEPTH;
|
|
14
|
+
// Null/undefined → empty object (many methods accept no params)
|
|
15
|
+
if (params == null)
|
|
16
|
+
return {};
|
|
17
|
+
// Must be a plain object
|
|
18
|
+
if (typeof params !== 'object' || Array.isArray(params)) {
|
|
19
|
+
throw new ValidationError('Params must be a plain object');
|
|
20
|
+
}
|
|
21
|
+
// Deep validate
|
|
22
|
+
validateValue(params, 'params', maxStr, maxArr, maxDep, 0);
|
|
23
|
+
return params;
|
|
24
|
+
}
|
|
25
|
+
function validateValue(value, path, maxStr, maxArr, maxDep, depth) {
|
|
26
|
+
if (depth > maxDep) {
|
|
27
|
+
throw new ValidationError(`Maximum nesting depth exceeded at ${path}`);
|
|
28
|
+
}
|
|
29
|
+
if (typeof value === 'string') {
|
|
30
|
+
if (value.length > maxStr) {
|
|
31
|
+
throw new ValidationError(`String field "${path}" exceeds maximum length of ${maxStr}`);
|
|
32
|
+
}
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
if (Array.isArray(value)) {
|
|
36
|
+
if (value.length > maxArr) {
|
|
37
|
+
throw new ValidationError(`Array field "${path}" exceeds maximum length of ${maxArr}`);
|
|
38
|
+
}
|
|
39
|
+
for (let i = 0; i < value.length; i++) {
|
|
40
|
+
validateValue(value[i], `${path}[${i}]`, maxStr, maxArr, maxDep, depth + 1);
|
|
41
|
+
}
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (typeof value === 'object' && value !== null) {
|
|
45
|
+
const entries = Object.entries(value);
|
|
46
|
+
for (const [key, val] of entries) {
|
|
47
|
+
validateValue(val, `${path}.${key}`, maxStr, maxArr, maxDep, depth + 1);
|
|
48
|
+
}
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
// Primitives (number, boolean, null) are fine
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Create a validated IPC handler wrapper. Validates params before passing to the handler.
|
|
55
|
+
*/
|
|
56
|
+
export function withValidation(handler, options) {
|
|
57
|
+
return (params) => {
|
|
58
|
+
const validated = validateParams(params, options);
|
|
59
|
+
return handler(validated);
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","sourceRoot":"","sources":["../../src/ipc/validation.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import type Database from 'better-sqlite3';
|
|
2
|
+
export interface WebhookConfig {
|
|
3
|
+
id?: number;
|
|
4
|
+
url: string;
|
|
5
|
+
events: string[];
|
|
6
|
+
secret?: string;
|
|
7
|
+
active?: boolean;
|
|
8
|
+
name?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface WebhookRecord {
|
|
11
|
+
id: number;
|
|
12
|
+
url: string;
|
|
13
|
+
events: string;
|
|
14
|
+
secret: string | null;
|
|
15
|
+
active: number;
|
|
16
|
+
name: string | null;
|
|
17
|
+
created_at: string;
|
|
18
|
+
}
|
|
19
|
+
export interface DeliveryRecord {
|
|
20
|
+
id: number;
|
|
21
|
+
webhook_id: number;
|
|
22
|
+
event: string;
|
|
23
|
+
payload: string;
|
|
24
|
+
status: number;
|
|
25
|
+
response: string | null;
|
|
26
|
+
attempts: number;
|
|
27
|
+
created_at: string;
|
|
28
|
+
}
|
|
29
|
+
export interface WebhookDeliveryResult {
|
|
30
|
+
webhookId: number;
|
|
31
|
+
url: string;
|
|
32
|
+
status: number;
|
|
33
|
+
success: boolean;
|
|
34
|
+
attempts: number;
|
|
35
|
+
}
|
|
36
|
+
export declare function runWebhookMigration(db: Database.Database): void;
|
|
37
|
+
export declare class WebhookService {
|
|
38
|
+
private db;
|
|
39
|
+
private logger;
|
|
40
|
+
private retryDelays;
|
|
41
|
+
constructor(db: Database.Database);
|
|
42
|
+
/** Register a new webhook endpoint. */
|
|
43
|
+
add(config: WebhookConfig): WebhookRecord;
|
|
44
|
+
/** Remove a webhook by ID. */
|
|
45
|
+
remove(id: number): boolean;
|
|
46
|
+
/** Get a single webhook by ID. */
|
|
47
|
+
get(id: number): WebhookRecord | null;
|
|
48
|
+
/** List all webhooks. */
|
|
49
|
+
list(): WebhookRecord[];
|
|
50
|
+
/** Toggle a webhook active/inactive. */
|
|
51
|
+
toggle(id: number, active: boolean): boolean;
|
|
52
|
+
/** Get delivery history for a webhook (most recent first). */
|
|
53
|
+
history(webhookId?: number, limit?: number): DeliveryRecord[];
|
|
54
|
+
/**
|
|
55
|
+
* Fire an event to all matching webhooks.
|
|
56
|
+
* Returns delivery results (non-blocking — fire and forget with retry).
|
|
57
|
+
*/
|
|
58
|
+
fire(event: string, data: unknown): Promise<WebhookDeliveryResult[]>;
|
|
59
|
+
/** Deliver a payload to a single webhook with retries. */
|
|
60
|
+
private deliver;
|
|
61
|
+
private recordDelivery;
|
|
62
|
+
private delay;
|
|
63
|
+
/** Cleanup old delivery records. */
|
|
64
|
+
cleanup(olderThanDays?: number): number;
|
|
65
|
+
}
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import { getLogger } from '../utils/logger.js';
|
|
3
|
+
// ── Migration ───────────────────────────────────────────
|
|
4
|
+
export function runWebhookMigration(db) {
|
|
5
|
+
db.exec(`
|
|
6
|
+
CREATE TABLE IF NOT EXISTS webhooks (
|
|
7
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
8
|
+
url TEXT NOT NULL,
|
|
9
|
+
events TEXT NOT NULL DEFAULT '[]',
|
|
10
|
+
secret TEXT,
|
|
11
|
+
active INTEGER NOT NULL DEFAULT 1,
|
|
12
|
+
name TEXT,
|
|
13
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
14
|
+
);
|
|
15
|
+
|
|
16
|
+
CREATE TABLE IF NOT EXISTS webhook_deliveries (
|
|
17
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
18
|
+
webhook_id INTEGER NOT NULL REFERENCES webhooks(id) ON DELETE CASCADE,
|
|
19
|
+
event TEXT NOT NULL,
|
|
20
|
+
payload TEXT NOT NULL,
|
|
21
|
+
status INTEGER NOT NULL DEFAULT 0,
|
|
22
|
+
response TEXT,
|
|
23
|
+
attempts INTEGER NOT NULL DEFAULT 0,
|
|
24
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_webhook_id ON webhook_deliveries(webhook_id);
|
|
28
|
+
CREATE INDEX IF NOT EXISTS idx_webhook_deliveries_created_at ON webhook_deliveries(created_at);
|
|
29
|
+
`);
|
|
30
|
+
}
|
|
31
|
+
// ── Service ─────────────────────────────────────────────
|
|
32
|
+
export class WebhookService {
|
|
33
|
+
db;
|
|
34
|
+
logger = getLogger();
|
|
35
|
+
retryDelays = [1000, 3000, 10000]; // 1s, 3s, 10s
|
|
36
|
+
constructor(db) {
|
|
37
|
+
this.db = db;
|
|
38
|
+
runWebhookMigration(db);
|
|
39
|
+
}
|
|
40
|
+
/** Register a new webhook endpoint. */
|
|
41
|
+
add(config) {
|
|
42
|
+
const stmt = this.db.prepare(`
|
|
43
|
+
INSERT INTO webhooks (url, events, secret, active, name)
|
|
44
|
+
VALUES (?, ?, ?, ?, ?)
|
|
45
|
+
`);
|
|
46
|
+
const info = stmt.run(config.url, JSON.stringify(config.events), config.secret ?? null, config.active !== false ? 1 : 0, config.name ?? null);
|
|
47
|
+
this.logger.info(`Webhook #${info.lastInsertRowid} registered: ${config.url}`);
|
|
48
|
+
return this.get(Number(info.lastInsertRowid));
|
|
49
|
+
}
|
|
50
|
+
/** Remove a webhook by ID. */
|
|
51
|
+
remove(id) {
|
|
52
|
+
const info = this.db.prepare('DELETE FROM webhooks WHERE id = ?').run(id);
|
|
53
|
+
return info.changes > 0;
|
|
54
|
+
}
|
|
55
|
+
/** Get a single webhook by ID. */
|
|
56
|
+
get(id) {
|
|
57
|
+
return this.db.prepare('SELECT * FROM webhooks WHERE id = ?').get(id);
|
|
58
|
+
}
|
|
59
|
+
/** List all webhooks. */
|
|
60
|
+
list() {
|
|
61
|
+
return this.db.prepare('SELECT * FROM webhooks ORDER BY created_at DESC').all();
|
|
62
|
+
}
|
|
63
|
+
/** Toggle a webhook active/inactive. */
|
|
64
|
+
toggle(id, active) {
|
|
65
|
+
const info = this.db.prepare('UPDATE webhooks SET active = ? WHERE id = ?').run(active ? 1 : 0, id);
|
|
66
|
+
return info.changes > 0;
|
|
67
|
+
}
|
|
68
|
+
/** Get delivery history for a webhook (most recent first). */
|
|
69
|
+
history(webhookId, limit = 50) {
|
|
70
|
+
if (webhookId) {
|
|
71
|
+
return this.db.prepare('SELECT * FROM webhook_deliveries WHERE webhook_id = ? ORDER BY created_at DESC LIMIT ?').all(webhookId, limit);
|
|
72
|
+
}
|
|
73
|
+
return this.db.prepare('SELECT * FROM webhook_deliveries ORDER BY created_at DESC LIMIT ?').all(limit);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Fire an event to all matching webhooks.
|
|
77
|
+
* Returns delivery results (non-blocking — fire and forget with retry).
|
|
78
|
+
*/
|
|
79
|
+
async fire(event, data) {
|
|
80
|
+
const webhooks = this.db.prepare('SELECT * FROM webhooks WHERE active = 1').all();
|
|
81
|
+
const matching = webhooks.filter(wh => {
|
|
82
|
+
const events = JSON.parse(wh.events);
|
|
83
|
+
return events.includes('*') || events.includes(event);
|
|
84
|
+
});
|
|
85
|
+
if (matching.length === 0)
|
|
86
|
+
return [];
|
|
87
|
+
const payload = JSON.stringify({ event, data, timestamp: new Date().toISOString() });
|
|
88
|
+
const results = [];
|
|
89
|
+
await Promise.all(matching.map(async (wh) => {
|
|
90
|
+
const result = await this.deliver(wh, event, payload);
|
|
91
|
+
results.push(result);
|
|
92
|
+
}));
|
|
93
|
+
return results;
|
|
94
|
+
}
|
|
95
|
+
/** Deliver a payload to a single webhook with retries. */
|
|
96
|
+
async deliver(wh, event, payload) {
|
|
97
|
+
let lastStatus = 0;
|
|
98
|
+
let lastResponse = null;
|
|
99
|
+
let attempts = 0;
|
|
100
|
+
for (let i = 0; i <= this.retryDelays.length; i++) {
|
|
101
|
+
attempts++;
|
|
102
|
+
try {
|
|
103
|
+
const headers = {
|
|
104
|
+
'Content-Type': 'application/json',
|
|
105
|
+
'X-Webhook-Event': event,
|
|
106
|
+
};
|
|
107
|
+
// HMAC signing
|
|
108
|
+
if (wh.secret) {
|
|
109
|
+
const signature = crypto
|
|
110
|
+
.createHmac('sha256', wh.secret)
|
|
111
|
+
.update(payload)
|
|
112
|
+
.digest('hex');
|
|
113
|
+
headers['X-Webhook-Signature'] = `sha256=${signature}`;
|
|
114
|
+
}
|
|
115
|
+
const response = await fetch(wh.url, {
|
|
116
|
+
method: 'POST',
|
|
117
|
+
headers,
|
|
118
|
+
body: payload,
|
|
119
|
+
signal: AbortSignal.timeout(10_000),
|
|
120
|
+
});
|
|
121
|
+
lastStatus = response.status;
|
|
122
|
+
lastResponse = await response.text().catch(() => null);
|
|
123
|
+
if (response.ok) {
|
|
124
|
+
this.recordDelivery(wh.id, event, payload, lastStatus, lastResponse, attempts);
|
|
125
|
+
return { webhookId: wh.id, url: wh.url, status: lastStatus, success: true, attempts };
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
catch (err) {
|
|
129
|
+
lastStatus = 0;
|
|
130
|
+
lastResponse = err instanceof Error ? err.message : String(err);
|
|
131
|
+
}
|
|
132
|
+
// Retry delay (don't delay after last attempt)
|
|
133
|
+
if (i < this.retryDelays.length) {
|
|
134
|
+
await this.delay(this.retryDelays[i]);
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
// All retries exhausted
|
|
138
|
+
this.recordDelivery(wh.id, event, payload, lastStatus, lastResponse, attempts);
|
|
139
|
+
this.logger.warn(`Webhook #${wh.id} delivery failed after ${attempts} attempts: ${wh.url}`);
|
|
140
|
+
return { webhookId: wh.id, url: wh.url, status: lastStatus, success: false, attempts };
|
|
141
|
+
}
|
|
142
|
+
recordDelivery(webhookId, event, payload, status, response, attempts) {
|
|
143
|
+
this.db.prepare(`
|
|
144
|
+
INSERT INTO webhook_deliveries (webhook_id, event, payload, status, response, attempts)
|
|
145
|
+
VALUES (?, ?, ?, ?, ?, ?)
|
|
146
|
+
`).run(webhookId, event, payload, status, response, attempts);
|
|
147
|
+
}
|
|
148
|
+
delay(ms) {
|
|
149
|
+
return new Promise(resolve => setTimeout(resolve, ms));
|
|
150
|
+
}
|
|
151
|
+
/** Cleanup old delivery records. */
|
|
152
|
+
cleanup(olderThanDays = 30) {
|
|
153
|
+
const info = this.db.prepare(`DELETE FROM webhook_deliveries WHERE created_at < datetime('now', '-' || ? || ' days')`).run(olderThanDays);
|
|
154
|
+
return info.changes;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/webhooks/service.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@timmeck/brain-core",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.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,6 +20,9 @@
|
|
|
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
|
+
"./api/middleware": "./dist/api/middleware.js",
|
|
24
|
+
"./ipc/validation": "./dist/ipc/validation.js",
|
|
25
|
+
"./ipc/errors": "./dist/ipc/errors.js",
|
|
23
26
|
"./math/wilson-score": "./dist/math/wilson-score.js",
|
|
24
27
|
"./math/time-decay": "./dist/math/time-decay.js",
|
|
25
28
|
"./config/loader": "./dist/config/loader.js",
|
|
@@ -37,7 +40,10 @@
|
|
|
37
40
|
"./ecosystem/service": "./dist/ecosystem/service.js",
|
|
38
41
|
"./memory/types": "./dist/memory/types.js",
|
|
39
42
|
"./memory/base-memory-engine": "./dist/memory/base-memory-engine.js",
|
|
40
|
-
"./embeddings/engine": "./dist/embeddings/engine.js"
|
|
43
|
+
"./embeddings/engine": "./dist/embeddings/engine.js",
|
|
44
|
+
"./webhooks/service": "./dist/webhooks/service.js",
|
|
45
|
+
"./export/service": "./dist/export/service.js",
|
|
46
|
+
"./backup/service": "./dist/backup/service.js"
|
|
41
47
|
},
|
|
42
48
|
"scripts": {
|
|
43
49
|
"build": "tsc",
|