@sparkleideas/ruv-swarm 1.0.18-patch.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1565 -0
- package/bin/ruv-swarm-clean.js +1872 -0
- package/bin/ruv-swarm-memory.js +119 -0
- package/bin/ruv-swarm-secure-heartbeat.js +1549 -0
- package/bin/ruv-swarm-secure.js +1689 -0
- package/package.json +221 -0
- package/src/agent.ts +342 -0
- package/src/benchmark.js +267 -0
- package/src/claude-flow-enhanced.js +839 -0
- package/src/claude-integration/advanced-commands.js +561 -0
- package/src/claude-integration/core.js +112 -0
- package/src/claude-integration/docs.js +1548 -0
- package/src/claude-integration/env-template.js +39 -0
- package/src/claude-integration/index.js +209 -0
- package/src/claude-integration/remote.js +408 -0
- package/src/cli-diagnostics.js +364 -0
- package/src/cognitive-pattern-evolution.js +1317 -0
- package/src/daa-cognition.js +977 -0
- package/src/daa-service.d.ts +298 -0
- package/src/daa-service.js +1116 -0
- package/src/diagnostics.js +533 -0
- package/src/errors.js +528 -0
- package/src/github-coordinator/README.md +193 -0
- package/src/github-coordinator/claude-hooks.js +162 -0
- package/src/github-coordinator/gh-cli-coordinator.js +260 -0
- package/src/hooks/cli.js +82 -0
- package/src/hooks/index.js +1900 -0
- package/src/index-enhanced.d.ts +371 -0
- package/src/index-enhanced.js +734 -0
- package/src/index.d.ts +287 -0
- package/src/index.js +405 -0
- package/src/index.ts +457 -0
- package/src/logger.js +182 -0
- package/src/logging-config.js +179 -0
- package/src/mcp-daa-tools.js +735 -0
- package/src/mcp-tools-benchmarks.js +328 -0
- package/src/mcp-tools-enhanced.js +2863 -0
- package/src/memory-config.js +42 -0
- package/src/meta-learning-framework.js +1359 -0
- package/src/neural-agent.js +830 -0
- package/src/neural-coordination-protocol.js +1363 -0
- package/src/neural-models/README.md +118 -0
- package/src/neural-models/autoencoder.js +543 -0
- package/src/neural-models/base.js +269 -0
- package/src/neural-models/cnn.js +497 -0
- package/src/neural-models/gnn.js +447 -0
- package/src/neural-models/gru.js +536 -0
- package/src/neural-models/index.js +273 -0
- package/src/neural-models/lstm.js +551 -0
- package/src/neural-models/neural-presets-complete.js +1306 -0
- package/src/neural-models/presets/graph.js +392 -0
- package/src/neural-models/presets/index.js +279 -0
- package/src/neural-models/presets/nlp.js +328 -0
- package/src/neural-models/presets/timeseries.js +368 -0
- package/src/neural-models/presets/vision.js +387 -0
- package/src/neural-models/resnet.js +534 -0
- package/src/neural-models/transformer.js +515 -0
- package/src/neural-models/vae.js +489 -0
- package/src/neural-network-manager.js +1938 -0
- package/src/neural-network.ts +296 -0
- package/src/neural.js +574 -0
- package/src/performance-benchmarks.js +898 -0
- package/src/performance.js +458 -0
- package/src/persistence-pooled.js +695 -0
- package/src/persistence.js +480 -0
- package/src/schemas.js +864 -0
- package/src/security.js +218 -0
- package/src/singleton-container.js +183 -0
- package/src/sqlite-pool.js +587 -0
- package/src/sqlite-worker.js +141 -0
- package/src/types.ts +164 -0
- package/src/utils.ts +286 -0
- package/src/wasm-loader.js +601 -0
- package/src/wasm-loader2.js +404 -0
- package/src/wasm-memory-optimizer.js +783 -0
- package/src/wasm-types.d.ts +63 -0
- package/wasm/README.md +347 -0
- package/wasm/neuro-divergent.wasm +0 -0
- package/wasm/package.json +18 -0
- package/wasm/ruv-fann.wasm +0 -0
- package/wasm/ruv_swarm_simd.wasm +0 -0
- package/wasm/ruv_swarm_wasm.d.ts +391 -0
- package/wasm/ruv_swarm_wasm.js +2164 -0
- package/wasm/ruv_swarm_wasm_bg.wasm +0 -0
- package/wasm/ruv_swarm_wasm_bg.wasm.d.ts +123 -0
- package/wasm/wasm-bindings-loader.mjs +435 -0
- package/wasm/wasm-updates.md +684 -0
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* High-Availability SQLite Connection Pool for @sparkleideas/ruv-swarm
|
|
3
|
+
*
|
|
4
|
+
* This implementation addresses the production readiness concerns:
|
|
5
|
+
* - Connection exhaustion prevention
|
|
6
|
+
* - Deadlock avoidance under load
|
|
7
|
+
* - System availability during high concurrency
|
|
8
|
+
* - Proper resource lifecycle management
|
|
9
|
+
*
|
|
10
|
+
* Design decisions:
|
|
11
|
+
* - Single primary connection for writes (SQLite single-writer limitation)
|
|
12
|
+
* - Multiple reader connections in WAL mode for concurrent reads
|
|
13
|
+
* - Worker thread pool for CPU-intensive queries
|
|
14
|
+
* - Connection health monitoring and auto-recovery
|
|
15
|
+
* - Graceful degradation under pressure
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import Database from 'better-sqlite3';
|
|
19
|
+
import { Worker } from 'worker_threads';
|
|
20
|
+
import { EventEmitter } from 'events';
|
|
21
|
+
import path from 'path';
|
|
22
|
+
import fs from 'fs';
|
|
23
|
+
import os from 'os';
|
|
24
|
+
|
|
25
|
+
class SQLiteConnectionPool extends EventEmitter {
|
|
26
|
+
constructor(dbPath, options = {}) {
|
|
27
|
+
super();
|
|
28
|
+
|
|
29
|
+
this.dbPath = dbPath;
|
|
30
|
+
this.options = {
|
|
31
|
+
// Pool configuration
|
|
32
|
+
maxReaders: options.maxReaders || Math.max(4, os.cpus().length),
|
|
33
|
+
maxWorkers: options.maxWorkers || Math.max(2, Math.floor(os.cpus().length / 2)),
|
|
34
|
+
|
|
35
|
+
// Connection timeouts
|
|
36
|
+
acquireTimeout: options.acquireTimeout || 30000, // 30 seconds
|
|
37
|
+
idleTimeout: options.idleTimeout || 300000, // 5 minutes
|
|
38
|
+
|
|
39
|
+
// Health monitoring
|
|
40
|
+
healthCheckInterval: options.healthCheckInterval || 60000, // 1 minute
|
|
41
|
+
maxRetries: options.maxRetries || 3,
|
|
42
|
+
|
|
43
|
+
// Performance settings
|
|
44
|
+
mmapSize: options.mmapSize || 268435456, // 256MB
|
|
45
|
+
cacheSize: options.cacheSize || -64000, // 64MB
|
|
46
|
+
|
|
47
|
+
// High availability
|
|
48
|
+
enableBackup: options.enableBackup || false,
|
|
49
|
+
backupInterval: options.backupInterval || 3600000, // 1 hour
|
|
50
|
+
|
|
51
|
+
...options
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
// Connection pools
|
|
55
|
+
this.writeConnection = null;
|
|
56
|
+
this.readerConnections = [];
|
|
57
|
+
this.availableReaders = [];
|
|
58
|
+
this.busyReaders = new Set();
|
|
59
|
+
|
|
60
|
+
// Worker thread pool
|
|
61
|
+
this.workers = [];
|
|
62
|
+
this.availableWorkers = [];
|
|
63
|
+
this.busyWorkers = new Set();
|
|
64
|
+
|
|
65
|
+
// Request queues
|
|
66
|
+
this.readQueue = [];
|
|
67
|
+
this.writeQueue = [];
|
|
68
|
+
this.workerQueue = [];
|
|
69
|
+
|
|
70
|
+
// Health monitoring
|
|
71
|
+
this.isHealthy = true;
|
|
72
|
+
this.healthCheckTimer = null;
|
|
73
|
+
this.lastHealthCheck = Date.now();
|
|
74
|
+
|
|
75
|
+
// Statistics
|
|
76
|
+
this.stats = {
|
|
77
|
+
totalReads: 0,
|
|
78
|
+
totalWrites: 0,
|
|
79
|
+
totalWorkerTasks: 0,
|
|
80
|
+
failedConnections: 0,
|
|
81
|
+
averageReadTime: 0,
|
|
82
|
+
averageWriteTime: 0,
|
|
83
|
+
activeConnections: 0
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
// Prepared statements cache
|
|
87
|
+
this.preparedStatements = new Map();
|
|
88
|
+
|
|
89
|
+
this.initialize();
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async initialize() {
|
|
93
|
+
try {
|
|
94
|
+
// Ensure database directory exists
|
|
95
|
+
const dataDir = path.dirname(this.dbPath);
|
|
96
|
+
if (!fs.existsSync(dataDir)) {
|
|
97
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Initialize write connection
|
|
101
|
+
await this.initializeWriteConnection();
|
|
102
|
+
|
|
103
|
+
// Initialize reader connections
|
|
104
|
+
await this.initializeReaderConnections();
|
|
105
|
+
|
|
106
|
+
// Initialize worker threads
|
|
107
|
+
await this.initializeWorkerThreads();
|
|
108
|
+
|
|
109
|
+
// Start health monitoring
|
|
110
|
+
this.startHealthMonitoring();
|
|
111
|
+
|
|
112
|
+
// Setup cleanup handlers
|
|
113
|
+
this.setupCleanupHandlers();
|
|
114
|
+
|
|
115
|
+
this.emit('ready');
|
|
116
|
+
} catch (error) {
|
|
117
|
+
this.emit('error', error);
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async initializeWriteConnection() {
|
|
123
|
+
try {
|
|
124
|
+
this.writeConnection = new Database(this.dbPath);
|
|
125
|
+
this.configureConnection(this.writeConnection);
|
|
126
|
+
|
|
127
|
+
// Test connection
|
|
128
|
+
this.writeConnection.prepare('SELECT 1').get();
|
|
129
|
+
|
|
130
|
+
this.emit('write-connection-ready');
|
|
131
|
+
} catch (error) {
|
|
132
|
+
this.stats.failedConnections++;
|
|
133
|
+
throw new Error(`Failed to initialize write connection: ${error.message}`);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async initializeReaderConnections() {
|
|
138
|
+
for (let i = 0; i < this.options.maxReaders; i++) {
|
|
139
|
+
try {
|
|
140
|
+
const reader = new Database(this.dbPath, { readonly: true });
|
|
141
|
+
this.configureReadOnlyConnection(reader);
|
|
142
|
+
|
|
143
|
+
// Test connection
|
|
144
|
+
reader.prepare('SELECT 1').get();
|
|
145
|
+
|
|
146
|
+
this.readerConnections.push(reader);
|
|
147
|
+
this.availableReaders.push(reader);
|
|
148
|
+
|
|
149
|
+
this.emit('reader-connection-ready', i);
|
|
150
|
+
} catch (error) {
|
|
151
|
+
this.stats.failedConnections++;
|
|
152
|
+
console.error(`Failed to initialize reader connection ${i}:`, error);
|
|
153
|
+
// Continue with fewer readers rather than failing completely
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (this.availableReaders.length === 0) {
|
|
158
|
+
throw new Error('Failed to initialize any reader connections');
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async initializeWorkerThreads() {
|
|
163
|
+
const workerScript = path.join(path.dirname(new URL(import.meta.url).pathname), 'sqlite-worker.js');
|
|
164
|
+
|
|
165
|
+
for (let i = 0; i < this.options.maxWorkers; i++) {
|
|
166
|
+
try {
|
|
167
|
+
const worker = new Worker(workerScript, {
|
|
168
|
+
workerData: {
|
|
169
|
+
dbPath: this.dbPath,
|
|
170
|
+
options: this.options
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
worker.on('error', (error) => {
|
|
175
|
+
this.emit('worker-error', error);
|
|
176
|
+
this.handleWorkerError(worker, error);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
worker.on('exit', (code) => {
|
|
180
|
+
if (code !== 0) {
|
|
181
|
+
this.emit('worker-exit', code);
|
|
182
|
+
this.handleWorkerExit(worker, code);
|
|
183
|
+
}
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
this.workers.push(worker);
|
|
187
|
+
this.availableWorkers.push(worker);
|
|
188
|
+
|
|
189
|
+
this.emit('worker-ready', i);
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error(`Failed to initialize worker ${i}:`, error);
|
|
192
|
+
// Continue with fewer workers rather than failing completely
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
configureConnection(db) {
|
|
198
|
+
// Essential SQLite optimizations for high availability
|
|
199
|
+
db.pragma('journal_mode = WAL'); // Enable WAL mode for concurrent reads
|
|
200
|
+
db.pragma('synchronous = NORMAL'); // Balance safety and performance
|
|
201
|
+
db.pragma('temp_store = MEMORY'); // Use memory for temp tables
|
|
202
|
+
db.pragma('mmap_size = ' + this.options.mmapSize);
|
|
203
|
+
db.pragma('cache_size = ' + this.options.cacheSize);
|
|
204
|
+
db.pragma('foreign_keys = ON'); // Enable foreign key constraints
|
|
205
|
+
db.pragma('busy_timeout = 5000'); // 5 second timeout for busy database
|
|
206
|
+
|
|
207
|
+
// Optimize query planner
|
|
208
|
+
db.pragma('optimize');
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
configureReadOnlyConnection(db) {
|
|
212
|
+
// Configuration for readonly connections - limited pragma statements
|
|
213
|
+
try {
|
|
214
|
+
// These are safe for readonly connections
|
|
215
|
+
db.pragma('temp_store = MEMORY'); // Use memory for temp tables
|
|
216
|
+
db.pragma('cache_size = ' + this.options.cacheSize);
|
|
217
|
+
db.pragma('busy_timeout = 5000'); // 5 second timeout for busy database
|
|
218
|
+
} catch (error) {
|
|
219
|
+
// Some pragma statements might fail on readonly connections
|
|
220
|
+
// This is expected behavior, continue without them
|
|
221
|
+
console.debug('Some pragma statements skipped for readonly connection:', error.message);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Read operation with connection pooling
|
|
226
|
+
async read(sql, params = []) {
|
|
227
|
+
const startTime = Date.now();
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const connection = await this.acquireReaderConnection();
|
|
231
|
+
const stmt = this.getPreparedStatement(connection, sql);
|
|
232
|
+
const result = stmt.all(params);
|
|
233
|
+
|
|
234
|
+
this.releaseReaderConnection(connection);
|
|
235
|
+
|
|
236
|
+
// Update statistics
|
|
237
|
+
this.stats.totalReads++;
|
|
238
|
+
this.updateAverageTime('read', Date.now() - startTime);
|
|
239
|
+
|
|
240
|
+
return result;
|
|
241
|
+
} catch (error) {
|
|
242
|
+
this.emit('read-error', error);
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Write operation with queuing
|
|
248
|
+
async write(sql, params = []) {
|
|
249
|
+
return new Promise((resolve, reject) => {
|
|
250
|
+
this.writeQueue.push({
|
|
251
|
+
sql,
|
|
252
|
+
params,
|
|
253
|
+
resolve,
|
|
254
|
+
reject,
|
|
255
|
+
timestamp: Date.now()
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
this.processWriteQueue();
|
|
259
|
+
});
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Transaction support
|
|
263
|
+
async transaction(fn) {
|
|
264
|
+
const startTime = Date.now();
|
|
265
|
+
|
|
266
|
+
try {
|
|
267
|
+
const result = this.writeConnection.transaction(fn)();
|
|
268
|
+
|
|
269
|
+
this.stats.totalWrites++;
|
|
270
|
+
this.updateAverageTime('write', Date.now() - startTime);
|
|
271
|
+
|
|
272
|
+
return result;
|
|
273
|
+
} catch (error) {
|
|
274
|
+
this.emit('transaction-error', error);
|
|
275
|
+
throw error;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// CPU-intensive query execution in worker thread
|
|
280
|
+
async executeInWorker(sql, params = []) {
|
|
281
|
+
return new Promise((resolve, reject) => {
|
|
282
|
+
this.workerQueue.push({
|
|
283
|
+
sql,
|
|
284
|
+
params,
|
|
285
|
+
resolve,
|
|
286
|
+
reject,
|
|
287
|
+
timestamp: Date.now()
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
this.processWorkerQueue();
|
|
291
|
+
});
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
async acquireReaderConnection() {
|
|
295
|
+
return new Promise((resolve, reject) => {
|
|
296
|
+
if (this.availableReaders.length > 0) {
|
|
297
|
+
const connection = this.availableReaders.pop();
|
|
298
|
+
this.busyReaders.add(connection);
|
|
299
|
+
resolve(connection);
|
|
300
|
+
} else {
|
|
301
|
+
// Queue the request
|
|
302
|
+
this.readQueue.push({ resolve, reject, timestamp: Date.now() });
|
|
303
|
+
|
|
304
|
+
// Timeout handling
|
|
305
|
+
setTimeout(() => {
|
|
306
|
+
const index = this.readQueue.findIndex(item => item.resolve === resolve);
|
|
307
|
+
if (index !== -1) {
|
|
308
|
+
this.readQueue.splice(index, 1);
|
|
309
|
+
reject(new Error('Reader connection acquire timeout'));
|
|
310
|
+
}
|
|
311
|
+
}, this.options.acquireTimeout);
|
|
312
|
+
}
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
releaseReaderConnection(connection) {
|
|
317
|
+
if (this.busyReaders.has(connection)) {
|
|
318
|
+
this.busyReaders.delete(connection);
|
|
319
|
+
|
|
320
|
+
// Process queued read requests
|
|
321
|
+
if (this.readQueue.length > 0) {
|
|
322
|
+
const { resolve } = this.readQueue.shift();
|
|
323
|
+
this.busyReaders.add(connection);
|
|
324
|
+
resolve(connection);
|
|
325
|
+
} else {
|
|
326
|
+
this.availableReaders.push(connection);
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
async processWriteQueue() {
|
|
332
|
+
if (this.writeQueue.length === 0) return;
|
|
333
|
+
|
|
334
|
+
const { sql, params, resolve, reject, timestamp } = this.writeQueue.shift();
|
|
335
|
+
const startTime = Date.now();
|
|
336
|
+
|
|
337
|
+
try {
|
|
338
|
+
// Check for timeout
|
|
339
|
+
if (Date.now() - timestamp > this.options.acquireTimeout) {
|
|
340
|
+
reject(new Error('Write operation timeout'));
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
const stmt = this.getPreparedStatement(this.writeConnection, sql);
|
|
345
|
+
const result = stmt.run(params);
|
|
346
|
+
|
|
347
|
+
this.stats.totalWrites++;
|
|
348
|
+
this.updateAverageTime('write', Date.now() - startTime);
|
|
349
|
+
|
|
350
|
+
resolve(result);
|
|
351
|
+
} catch (error) {
|
|
352
|
+
this.emit('write-error', error);
|
|
353
|
+
reject(error);
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
// Process next item in queue
|
|
357
|
+
if (this.writeQueue.length > 0) {
|
|
358
|
+
setImmediate(() => this.processWriteQueue());
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async processWorkerQueue() {
|
|
363
|
+
if (this.workerQueue.length === 0 || this.availableWorkers.length === 0) return;
|
|
364
|
+
|
|
365
|
+
const worker = this.availableWorkers.pop();
|
|
366
|
+
const { sql, params, resolve, reject, timestamp } = this.workerQueue.shift();
|
|
367
|
+
|
|
368
|
+
try {
|
|
369
|
+
// Check for timeout
|
|
370
|
+
if (Date.now() - timestamp > this.options.acquireTimeout) {
|
|
371
|
+
reject(new Error('Worker task timeout'));
|
|
372
|
+
this.availableWorkers.push(worker);
|
|
373
|
+
return;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
this.busyWorkers.add(worker);
|
|
377
|
+
|
|
378
|
+
const messageHandler = (result) => {
|
|
379
|
+
this.busyWorkers.delete(worker);
|
|
380
|
+
this.availableWorkers.push(worker);
|
|
381
|
+
this.stats.totalWorkerTasks++;
|
|
382
|
+
|
|
383
|
+
if (result.error) {
|
|
384
|
+
reject(new Error(result.error));
|
|
385
|
+
} else {
|
|
386
|
+
resolve(result.data);
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
// Process next item in queue
|
|
390
|
+
if (this.workerQueue.length > 0) {
|
|
391
|
+
setImmediate(() => this.processWorkerQueue());
|
|
392
|
+
}
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
worker.once('message', messageHandler);
|
|
396
|
+
worker.postMessage({ sql, params });
|
|
397
|
+
|
|
398
|
+
} catch (error) {
|
|
399
|
+
this.busyWorkers.delete(worker);
|
|
400
|
+
this.availableWorkers.push(worker);
|
|
401
|
+
reject(error);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
getPreparedStatement(connection, sql) {
|
|
406
|
+
const key = `${connection._id || 'main'}_${sql}`;
|
|
407
|
+
if (!this.preparedStatements.has(key)) {
|
|
408
|
+
this.preparedStatements.set(key, connection.prepare(sql));
|
|
409
|
+
}
|
|
410
|
+
return this.preparedStatements.get(key);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
startHealthMonitoring() {
|
|
414
|
+
this.healthCheckTimer = setInterval(() => {
|
|
415
|
+
this.performHealthCheck();
|
|
416
|
+
}, this.options.healthCheckInterval);
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
async performHealthCheck() {
|
|
420
|
+
const startTime = Date.now();
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
// Check write connection
|
|
424
|
+
this.writeConnection.prepare('SELECT 1').get();
|
|
425
|
+
|
|
426
|
+
// Check reader connections
|
|
427
|
+
for (const reader of this.readerConnections) {
|
|
428
|
+
reader.prepare('SELECT 1').get();
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// Check worker threads
|
|
432
|
+
const workerHealthPromises = this.workers.map(worker => {
|
|
433
|
+
return new Promise((resolve) => {
|
|
434
|
+
const timeout = setTimeout(() => resolve(false), 1000);
|
|
435
|
+
worker.once('message', (result) => {
|
|
436
|
+
clearTimeout(timeout);
|
|
437
|
+
resolve(result.health === 'ok');
|
|
438
|
+
});
|
|
439
|
+
worker.postMessage({ health: true });
|
|
440
|
+
});
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
const workerHealthResults = await Promise.all(workerHealthPromises);
|
|
444
|
+
const healthyWorkers = workerHealthResults.filter(Boolean).length;
|
|
445
|
+
|
|
446
|
+
this.isHealthy = healthyWorkers >= Math.floor(this.options.maxWorkers / 2);
|
|
447
|
+
this.lastHealthCheck = Date.now();
|
|
448
|
+
|
|
449
|
+
this.emit('health-check', {
|
|
450
|
+
healthy: this.isHealthy,
|
|
451
|
+
duration: Date.now() - startTime,
|
|
452
|
+
workers: healthyWorkers,
|
|
453
|
+
readers: this.availableReaders.length,
|
|
454
|
+
stats: this.getStats()
|
|
455
|
+
});
|
|
456
|
+
|
|
457
|
+
} catch (error) {
|
|
458
|
+
this.isHealthy = false;
|
|
459
|
+
this.emit('health-check-error', error);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
handleWorkerError(worker, error) {
|
|
464
|
+
// Remove failed worker from pools
|
|
465
|
+
this.busyWorkers.delete(worker);
|
|
466
|
+
const index = this.availableWorkers.indexOf(worker);
|
|
467
|
+
if (index !== -1) {
|
|
468
|
+
this.availableWorkers.splice(index, 1);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
// Attempt to create replacement worker
|
|
472
|
+
this.createReplacementWorker();
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
handleWorkerExit(worker, code) {
|
|
476
|
+
// Remove exited worker from pools
|
|
477
|
+
this.busyWorkers.delete(worker);
|
|
478
|
+
const index = this.availableWorkers.indexOf(worker);
|
|
479
|
+
if (index !== -1) {
|
|
480
|
+
this.availableWorkers.splice(index, 1);
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
// Attempt to create replacement worker
|
|
484
|
+
this.createReplacementWorker();
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
async createReplacementWorker() {
|
|
488
|
+
try {
|
|
489
|
+
const workerScript = path.join(path.dirname(new URL(import.meta.url).pathname), 'sqlite-worker.js');
|
|
490
|
+
const worker = new Worker(workerScript, {
|
|
491
|
+
workerData: {
|
|
492
|
+
dbPath: this.dbPath,
|
|
493
|
+
options: this.options
|
|
494
|
+
}
|
|
495
|
+
});
|
|
496
|
+
|
|
497
|
+
worker.on('error', (error) => {
|
|
498
|
+
this.emit('worker-error', error);
|
|
499
|
+
this.handleWorkerError(worker, error);
|
|
500
|
+
});
|
|
501
|
+
|
|
502
|
+
worker.on('exit', (code) => {
|
|
503
|
+
if (code !== 0) {
|
|
504
|
+
this.emit('worker-exit', code);
|
|
505
|
+
this.handleWorkerExit(worker, code);
|
|
506
|
+
}
|
|
507
|
+
});
|
|
508
|
+
|
|
509
|
+
this.workers.push(worker);
|
|
510
|
+
this.availableWorkers.push(worker);
|
|
511
|
+
|
|
512
|
+
this.emit('worker-replaced');
|
|
513
|
+
} catch (error) {
|
|
514
|
+
this.emit('worker-replacement-failed', error);
|
|
515
|
+
}
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
updateAverageTime(type, time) {
|
|
519
|
+
const statKey = `average${type.charAt(0).toUpperCase() + type.slice(1)}Time`;
|
|
520
|
+
const countKey = `total${type.charAt(0).toUpperCase() + type.slice(1)}s`;
|
|
521
|
+
|
|
522
|
+
if (this.stats[statKey] === 0) {
|
|
523
|
+
this.stats[statKey] = time;
|
|
524
|
+
} else {
|
|
525
|
+
this.stats[statKey] = (this.stats[statKey] + time) / 2;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
getStats() {
|
|
530
|
+
return {
|
|
531
|
+
...this.stats,
|
|
532
|
+
activeConnections: this.busyReaders.size + (this.writeConnection ? 1 : 0),
|
|
533
|
+
availableReaders: this.availableReaders.length,
|
|
534
|
+
availableWorkers: this.availableWorkers.length,
|
|
535
|
+
readQueueLength: this.readQueue.length,
|
|
536
|
+
writeQueueLength: this.writeQueue.length,
|
|
537
|
+
workerQueueLength: this.workerQueue.length,
|
|
538
|
+
isHealthy: this.isHealthy,
|
|
539
|
+
lastHealthCheck: this.lastHealthCheck
|
|
540
|
+
};
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
setupCleanupHandlers() {
|
|
544
|
+
const cleanup = () => {
|
|
545
|
+
this.close();
|
|
546
|
+
};
|
|
547
|
+
|
|
548
|
+
process.on('exit', cleanup);
|
|
549
|
+
process.on('SIGINT', cleanup);
|
|
550
|
+
process.on('SIGTERM', cleanup);
|
|
551
|
+
process.on('uncaughtException', cleanup);
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
async close() {
|
|
555
|
+
try {
|
|
556
|
+
// Stop health monitoring
|
|
557
|
+
if (this.healthCheckTimer) {
|
|
558
|
+
clearInterval(this.healthCheckTimer);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
// Close all connections
|
|
562
|
+
if (this.writeConnection) {
|
|
563
|
+
this.writeConnection.close();
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
for (const reader of this.readerConnections) {
|
|
567
|
+
reader.close();
|
|
568
|
+
}
|
|
569
|
+
|
|
570
|
+
// Terminate worker threads
|
|
571
|
+
for (const worker of this.workers) {
|
|
572
|
+
await worker.terminate();
|
|
573
|
+
}
|
|
574
|
+
|
|
575
|
+
// Clear all queues
|
|
576
|
+
this.readQueue.length = 0;
|
|
577
|
+
this.writeQueue.length = 0;
|
|
578
|
+
this.workerQueue.length = 0;
|
|
579
|
+
|
|
580
|
+
this.emit('closed');
|
|
581
|
+
} catch (error) {
|
|
582
|
+
this.emit('close-error', error);
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
export { SQLiteConnectionPool };
|