@timmeck/brain-core 2.2.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.js +1 -0
- package/dist/api/middleware.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 +6 -0
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- 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 +5 -2
package/dist/api/middleware.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/api/middleware.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../../src/api/middleware.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
|
@@ -53,3 +53,9 @@ export { CrossBrainCorrelator } from './cross-brain/correlator.js';
|
|
|
53
53
|
export type { CorrelatorEvent, Correlation, EcosystemHealth, CorrelatorConfig } from './cross-brain/correlator.js';
|
|
54
54
|
export { EcosystemService } from './ecosystem/service.js';
|
|
55
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
|
@@ -45,4 +45,10 @@ export { CrossBrainSubscriptionManager } from './cross-brain/subscription.js';
|
|
|
45
45
|
export { CrossBrainCorrelator } from './cross-brain/correlator.js';
|
|
46
46
|
// ── Ecosystem ──────────────────────────────────────────────
|
|
47
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';
|
|
48
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;AAC5C,OAAO,EAAE,cAAc,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErE,OAAO,EAAE,QAAQ,EAAE,eAAe,EAAE,aAAa,EAAE,YAAY,EAAE,uBAAuB,EAAE,MAAM,iBAAiB,CAAC;AAElH,8DAA8D;AAC9D,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAC;AAEjD,OAAO,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAGrD,8DAA8D;AAC9D,OAAO,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,QAAQ,EAAE,WAAW,EAAE,WAAW,EAAE,OAAO,EAAE,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AAEtH,8DAA8D;AAC9D,OAAO,EAAE,aAAa,EAAE,MAAM,iBAAiB,CAAC;AAEhD,OAAO,EAAE,WAAW,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAG3F,8DAA8D;AAC9D,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAEvD,8DAA8D;AAC9D,OAAO,EAAE,SAAS,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAQ/D,OAAO,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,qBAAqB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAC/D,OAAO,EAAE,QAAQ,EAAE,MAAM,0BAA0B,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,+BAA+B,CAAC;AAEnE,6DAA6D;AAC7D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAE/D,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAG/D,2DAA2D;AAC3D,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAC;AAU7D,OAAO,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAElE,4DAA4D;AAC5D,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAExD,OAAO,EAAE,kBAAkB,EAAE,MAAM,2BAA2B,CAAC;AAG/D,8DAA8D;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAC;AAE3D,OAAO,EAAE,kBAAkB,EAAE,MAAM,gCAAgC,CAAC;AAEpE,OAAO,EAAE,6BAA6B,EAAE,MAAM,+BAA+B,CAAC;AAE9E,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE,8DAA8D;AAC9D,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.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",
|
|
@@ -40,7 +40,10 @@
|
|
|
40
40
|
"./ecosystem/service": "./dist/ecosystem/service.js",
|
|
41
41
|
"./memory/types": "./dist/memory/types.js",
|
|
42
42
|
"./memory/base-memory-engine": "./dist/memory/base-memory-engine.js",
|
|
43
|
-
"./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"
|
|
44
47
|
},
|
|
45
48
|
"scripts": {
|
|
46
49
|
"build": "tsc",
|