@joystick.js/db-canary 0.0.0-canary.2274 ā 0.0.0-canary.2276
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 +87 -104
- package/debug_test_runner.js +208 -0
- package/dist/client/index.js +1 -1
- package/dist/server/cluster/master.js +2 -2
- package/dist/server/cluster/worker.js +1 -1
- package/dist/server/index.js +1 -1
- package/dist/server/lib/auto_index_manager.js +1 -1
- package/dist/server/lib/bulk_insert_optimizer.js +1 -1
- package/dist/server/lib/http_server.js +3 -3
- package/dist/server/lib/operation_dispatcher.js +1 -1
- package/dist/server/lib/operations/admin.js +1 -1
- package/dist/server/lib/operations/update_one.js +1 -1
- package/dist/server/lib/simple_sync_manager.js +1 -0
- package/dist/server/lib/sync_receiver.js +1 -0
- package/full_debug_test_runner.js +197 -0
- package/package.json +10 -7
- package/src/client/index.js +1 -0
- package/src/server/cluster/master.js +8 -2
- package/src/server/cluster/worker.js +9 -3
- package/src/server/index.js +25 -24
- package/src/server/lib/auto_index_manager.js +8 -3
- package/src/server/lib/bulk_insert_optimizer.js +79 -0
- package/src/server/lib/http_server.js +7 -0
- package/src/server/lib/operation_dispatcher.js +16 -10
- package/src/server/lib/operations/admin.js +64 -31
- package/src/server/lib/operations/update_one.js +251 -1
- package/src/server/lib/simple_sync_manager.js +444 -0
- package/src/server/lib/sync_receiver.js +461 -0
- package/tests/client/index.test.js +7 -0
- package/tests/performance/isolated_5000000_test.js +184 -0
- package/tests/server/lib/http_server.test.js +3 -12
- package/tests/server/lib/operations/update_one.test.js +161 -0
- package/tests/server/lib/simple_sync_system.test.js +124 -0
- package/dist/server/lib/replication_manager.js +0 -1
- package/dist/server/lib/write_forwarder.js +0 -1
- package/src/server/lib/replication_manager.js +0 -727
- package/src/server/lib/write_forwarder.js +0 -636
- package/tests/server/lib/replication_manager.test.js +0 -202
- package/tests/server/lib/write_forwarder.test.js +0 -258
package/README.md
CHANGED
|
@@ -213,11 +213,13 @@ export JOYSTICK_DB_SETTINGS='{
|
|
|
213
213
|
"window_ms": 300000
|
|
214
214
|
}
|
|
215
215
|
},
|
|
216
|
-
"
|
|
217
|
-
|
|
218
|
-
"
|
|
219
|
-
"
|
|
220
|
-
|
|
216
|
+
"primary": true,
|
|
217
|
+
"secondary_nodes": [
|
|
218
|
+
{ "ip": "192.168.1.100" },
|
|
219
|
+
{ "ip": "192.168.1.101" }
|
|
220
|
+
],
|
|
221
|
+
"secondary_sync_key": "/path/to/sync.key",
|
|
222
|
+
"sync_port": 1985,
|
|
221
223
|
"backup": {
|
|
222
224
|
"enabled": true,
|
|
223
225
|
"schedule": "0 2 * * *",
|
|
@@ -931,67 +933,58 @@ The HTTP API returns consistent error responses:
|
|
|
931
933
|
|
|
932
934
|
## Replication
|
|
933
935
|
|
|
934
|
-
|
|
936
|
+
JoystickDB features a simplified primary/secondary replication system for high availability and read scaling. The replication system maintains database separation across all nodes and uses API key authentication for secure sync operations.
|
|
935
937
|
|
|
936
|
-
### How Replication Works
|
|
938
|
+
### How Simplified Replication Works
|
|
937
939
|
|
|
938
|
-
- **Primary Node**:
|
|
939
|
-
- **Secondary Nodes**: Read-only copies that
|
|
940
|
-
- **
|
|
941
|
-
- **
|
|
942
|
-
- **
|
|
940
|
+
- **Primary Node**: Main database server that accepts both read and write operations for all databases
|
|
941
|
+
- **Secondary Nodes**: Read-only copies that receive synchronized data from primary for all databases
|
|
942
|
+
- **API Key Authentication**: All sync operations between nodes use API key authentication for security
|
|
943
|
+
- **Read-only Secondaries**: Secondary nodes block write operations from clients, only accepting authenticated sync from primary
|
|
944
|
+
- **Manual Failover**: Admin operations allow promoting a secondary to primary when needed
|
|
945
|
+
- **Database Isolation**: Replication maintains complete database separation across all nodes
|
|
943
946
|
|
|
944
|
-
### Setting Up Replication
|
|
947
|
+
### Setting Up Simplified Replication
|
|
945
948
|
|
|
946
949
|
#### 1. Configure Primary Node
|
|
947
950
|
|
|
948
951
|
```bash
|
|
949
|
-
# Primary server configuration
|
|
952
|
+
# Primary server configuration
|
|
950
953
|
export JOYSTICK_DB_SETTINGS='{
|
|
951
954
|
"port": 1983,
|
|
952
|
-
"
|
|
953
|
-
|
|
954
|
-
"
|
|
955
|
-
"
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
"id": "secondary-1",
|
|
960
|
-
"ip": "192.168.1.100",
|
|
961
|
-
"port": 1984,
|
|
962
|
-
"private_key": "base64-encoded-private-key",
|
|
963
|
-
"enabled": true
|
|
964
|
-
},
|
|
965
|
-
{
|
|
966
|
-
"id": "secondary-2",
|
|
967
|
-
"ip": "192.168.1.101",
|
|
968
|
-
"port": 1984,
|
|
969
|
-
"private_key": "base64-encoded-private-key",
|
|
970
|
-
"enabled": true
|
|
971
|
-
}
|
|
972
|
-
]
|
|
973
|
-
}
|
|
955
|
+
"primary": true,
|
|
956
|
+
"secondary_nodes": [
|
|
957
|
+
{ "ip": "192.168.1.100" },
|
|
958
|
+
{ "ip": "192.168.1.101" }
|
|
959
|
+
],
|
|
960
|
+
"secondary_sync_key": "/path/to/sync.key",
|
|
961
|
+
"sync_port": 1985
|
|
974
962
|
}'
|
|
975
963
|
```
|
|
976
964
|
|
|
977
965
|
#### 2. Configure Secondary Nodes
|
|
978
966
|
|
|
979
967
|
```bash
|
|
980
|
-
# Secondary server configuration
|
|
968
|
+
# Secondary server configuration
|
|
981
969
|
export JOYSTICK_DB_SETTINGS='{
|
|
982
|
-
"port":
|
|
983
|
-
"
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
"ip": "192.168.1.10",
|
|
987
|
-
"port": 1983,
|
|
988
|
-
"private_key": "base64-encoded-private-key"
|
|
989
|
-
}
|
|
990
|
-
}
|
|
970
|
+
"port": 1983,
|
|
971
|
+
"primary": false,
|
|
972
|
+
"secondary_sync_key": "/path/to/sync.key",
|
|
973
|
+
"sync_port": 1985
|
|
991
974
|
}'
|
|
992
975
|
```
|
|
993
976
|
|
|
994
|
-
|
|
977
|
+
#### 3. Create Sync Key File
|
|
978
|
+
|
|
979
|
+
The sync key file contains the API key for authenticated sync operations:
|
|
980
|
+
|
|
981
|
+
```bash
|
|
982
|
+
# Generate and save sync key (same key for all nodes)
|
|
983
|
+
echo "your-secure-sync-api-key" > /path/to/sync.key
|
|
984
|
+
chmod 600 /path/to/sync.key
|
|
985
|
+
```
|
|
986
|
+
|
|
987
|
+
### Managing Simplified Replication
|
|
995
988
|
|
|
996
989
|
```javascript
|
|
997
990
|
// Connect to primary node
|
|
@@ -1003,38 +996,35 @@ const client = joystickdb.client({
|
|
|
1003
996
|
}
|
|
1004
997
|
});
|
|
1005
998
|
|
|
1006
|
-
// Check
|
|
1007
|
-
const
|
|
1008
|
-
|
|
1009
|
-
console.log('š” Connected secondaries:', status.connected_secondaries);
|
|
1010
|
-
console.log('š Queue length:', status.queue_length);
|
|
1011
|
-
|
|
1012
|
-
// Add a new secondary node dynamically
|
|
1013
|
-
await client.add_secondary({
|
|
1014
|
-
id: 'secondary-3',
|
|
1015
|
-
ip: '192.168.1.102',
|
|
1016
|
-
port: 1984,
|
|
1017
|
-
private_key: 'base64-encoded-private-key'
|
|
999
|
+
// Check sync system status (admin operation)
|
|
1000
|
+
const sync_status = await client.admin_operation({
|
|
1001
|
+
operation: 'get_sync_status'
|
|
1018
1002
|
});
|
|
1019
1003
|
|
|
1020
|
-
|
|
1021
|
-
|
|
1022
|
-
console.log('
|
|
1023
|
-
console.log('š Total secondaries:', health.total_secondaries);
|
|
1004
|
+
console.log('š Sync enabled:', sync_status.enabled);
|
|
1005
|
+
console.log('š” Connected secondaries:', sync_status.connected_nodes);
|
|
1006
|
+
console.log('š Pending operations:', sync_status.pending_count);
|
|
1024
1007
|
|
|
1025
|
-
//
|
|
1026
|
-
await client.
|
|
1027
|
-
|
|
1008
|
+
// Promote secondary to primary (manual failover)
|
|
1009
|
+
const promote_result = await client.admin_operation({
|
|
1010
|
+
operation: 'promote_to_primary',
|
|
1011
|
+
secondary_ip: '192.168.1.100'
|
|
1012
|
+
});
|
|
1013
|
+
|
|
1014
|
+
console.log('š Failover completed:', promote_result.success);
|
|
1015
|
+
|
|
1016
|
+
// Force sync to all secondaries
|
|
1017
|
+
const sync_result = await client.admin_operation({
|
|
1018
|
+
operation: 'force_sync'
|
|
1019
|
+
});
|
|
1028
1020
|
|
|
1029
|
-
|
|
1030
|
-
await client.remove_secondary('secondary-1');
|
|
1031
|
-
console.log('ā Secondary removed');
|
|
1021
|
+
console.log('š Manual sync completed:', sync_result.synced_nodes);
|
|
1032
1022
|
```
|
|
1033
1023
|
|
|
1034
|
-
### Using Replication for Read Scaling
|
|
1024
|
+
### Using Simplified Replication for Read Scaling
|
|
1035
1025
|
|
|
1036
1026
|
```javascript
|
|
1037
|
-
// Connect to primary for writes
|
|
1027
|
+
// Connect to primary for writes (all databases supported)
|
|
1038
1028
|
const primary = joystickdb.client({
|
|
1039
1029
|
host: '192.168.1.10',
|
|
1040
1030
|
port: 1983,
|
|
@@ -1044,17 +1034,17 @@ const primary = joystickdb.client({
|
|
|
1044
1034
|
}
|
|
1045
1035
|
});
|
|
1046
1036
|
|
|
1047
|
-
// Connect to secondary for reads (
|
|
1037
|
+
// Connect to secondary for reads only (all databases replicated)
|
|
1048
1038
|
const secondary = joystickdb.client({
|
|
1049
1039
|
host: '192.168.1.100',
|
|
1050
|
-
port:
|
|
1040
|
+
port: 1983,
|
|
1051
1041
|
authentication: {
|
|
1052
1042
|
username: 'admin',
|
|
1053
1043
|
password: 'your-password'
|
|
1054
1044
|
}
|
|
1055
1045
|
});
|
|
1056
1046
|
|
|
1057
|
-
// Write to primary (
|
|
1047
|
+
// Write to primary (automatically synced to secondaries)
|
|
1058
1048
|
const user_db = primary.db('user_management');
|
|
1059
1049
|
const inventory_db = primary.db('inventory');
|
|
1060
1050
|
|
|
@@ -1068,7 +1058,7 @@ await inventory_db.collection('products').insert_one({
|
|
|
1068
1058
|
price: 99.99
|
|
1069
1059
|
});
|
|
1070
1060
|
|
|
1071
|
-
// Read from secondary (reduces load on primary
|
|
1061
|
+
// Read from secondary (reduces load on primary)
|
|
1072
1062
|
const secondary_user_db = secondary.db('user_management');
|
|
1073
1063
|
const secondary_inventory_db = secondary.db('inventory');
|
|
1074
1064
|
|
|
@@ -1077,6 +1067,13 @@ const products = await secondary_inventory_db.collection('products').find({});
|
|
|
1077
1067
|
|
|
1078
1068
|
console.log('š„ Users from secondary:', users.documents.length);
|
|
1079
1069
|
console.log('š¦ Products from secondary:', products.documents.length);
|
|
1070
|
+
|
|
1071
|
+
// Note: Write operations to secondary will be rejected
|
|
1072
|
+
try {
|
|
1073
|
+
await secondary_user_db.collection('users').insert_one({ name: 'Test' });
|
|
1074
|
+
} catch (error) {
|
|
1075
|
+
console.log('ā Secondary is read-only:', error.message);
|
|
1076
|
+
}
|
|
1080
1077
|
```
|
|
1081
1078
|
|
|
1082
1079
|
## Administration
|
|
@@ -1315,25 +1312,13 @@ setInterval(test_backup, 30 * 24 * 60 * 60 * 1000); // 30 days
|
|
|
1315
1312
|
}
|
|
1316
1313
|
},
|
|
1317
1314
|
|
|
1318
|
-
"
|
|
1319
|
-
|
|
1320
|
-
"
|
|
1321
|
-
"
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
},
|
|
1326
|
-
|
|
1327
|
-
"write_forwarder": {
|
|
1328
|
-
"enabled": false,
|
|
1329
|
-
"primary": {
|
|
1330
|
-
"ip": "127.0.0.1",
|
|
1331
|
-
"port": 1983,
|
|
1332
|
-
"private_key": "base64-encoded-key"
|
|
1333
|
-
},
|
|
1334
|
-
"timeout_ms": 5000,
|
|
1335
|
-
"retry_attempts": 3
|
|
1336
|
-
},
|
|
1315
|
+
"primary": true,
|
|
1316
|
+
"secondary_nodes": [
|
|
1317
|
+
{ "ip": "192.168.1.100" },
|
|
1318
|
+
{ "ip": "192.168.1.101" }
|
|
1319
|
+
],
|
|
1320
|
+
"secondary_sync_key": "/path/to/sync.key",
|
|
1321
|
+
"sync_port": 1985,
|
|
1337
1322
|
|
|
1338
1323
|
"s3": {
|
|
1339
1324
|
"region": "us-east-1",
|
|
@@ -1379,9 +1364,10 @@ JOYSTICKDB_DATABASE_PATH=./data
|
|
|
1379
1364
|
JOYSTICKDB_DATABASE_AUTO_MAP_SIZE=true
|
|
1380
1365
|
JOYSTICKDB_DATABASE_MAX_DBS=100
|
|
1381
1366
|
|
|
1382
|
-
# Replication settings
|
|
1383
|
-
|
|
1384
|
-
|
|
1367
|
+
# Simplified Replication settings
|
|
1368
|
+
JOYSTICKDB_PRIMARY=true
|
|
1369
|
+
JOYSTICKDB_SECONDARY_SYNC_KEY=/path/to/sync.key
|
|
1370
|
+
JOYSTICKDB_SYNC_PORT=1985
|
|
1385
1371
|
|
|
1386
1372
|
# S3 settings
|
|
1387
1373
|
JOYSTICKDB_S3_REGION=us-east-1
|
|
@@ -1675,13 +1661,10 @@ const monitor_performance = async () => {
|
|
|
1675
1661
|
#### Auto-Indexing Management
|
|
1676
1662
|
- `client.get_auto_index_stats()` - Get automatic indexing statistics
|
|
1677
1663
|
|
|
1678
|
-
#### Replication Management
|
|
1679
|
-
- `client.
|
|
1680
|
-
- `client.
|
|
1681
|
-
- `client.
|
|
1682
|
-
- `client.sync_secondaries()` - Force synchronization with secondaries
|
|
1683
|
-
- `client.get_secondary_health()` - Get health status of secondary nodes
|
|
1684
|
-
- `client.get_forwarder_status()` - Get write forwarder status (for secondary nodes)
|
|
1664
|
+
#### Simplified Replication Management (via Admin Operations)
|
|
1665
|
+
- `client.admin_operation({ operation: 'get_sync_status' })` - Get sync system status and statistics
|
|
1666
|
+
- `client.admin_operation({ operation: 'promote_to_primary', secondary_ip: 'ip' })` - Promote secondary to primary (manual failover)
|
|
1667
|
+
- `client.admin_operation({ operation: 'force_sync' })` - Force synchronization with all secondaries
|
|
1685
1668
|
|
|
1686
1669
|
#### Administration
|
|
1687
1670
|
- `client.get_stats()` - Get comprehensive server statistics
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @fileoverview Debug test runner for identifying uncaught exceptions in JoystickDB tests.
|
|
5
|
+
* This runner provides detailed exception tracking to identify problematic tests.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { spawn } from 'child_process';
|
|
9
|
+
import { existsSync } from 'fs';
|
|
10
|
+
|
|
11
|
+
// Track uncaught exceptions with detailed context
|
|
12
|
+
const uncaught_exceptions = [];
|
|
13
|
+
let current_test_context = 'startup';
|
|
14
|
+
|
|
15
|
+
// Enhanced exception handlers that log but don't exit
|
|
16
|
+
process.on('uncaughtException', (error) => {
|
|
17
|
+
const exception_info = {
|
|
18
|
+
type: 'uncaughtException',
|
|
19
|
+
context: current_test_context,
|
|
20
|
+
message: error.message,
|
|
21
|
+
stack: error.stack,
|
|
22
|
+
timestamp: new Date().toISOString()
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
uncaught_exceptions.push(exception_info);
|
|
26
|
+
|
|
27
|
+
console.error(`\nš„ UNCAUGHT EXCEPTION #${uncaught_exceptions.length}:`);
|
|
28
|
+
console.error(`š Context: ${current_test_context}`);
|
|
29
|
+
console.error(`š„ Error: ${error.message}`);
|
|
30
|
+
console.error(`š Stack: ${error.stack}`);
|
|
31
|
+
console.error(`ā° Time: ${exception_info.timestamp}\n`);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
35
|
+
const exception_info = {
|
|
36
|
+
type: 'unhandledRejection',
|
|
37
|
+
context: current_test_context,
|
|
38
|
+
reason: reason?.toString() || 'Unknown reason',
|
|
39
|
+
stack: reason?.stack || 'No stack available',
|
|
40
|
+
timestamp: new Date().toISOString()
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
uncaught_exceptions.push(exception_info);
|
|
44
|
+
|
|
45
|
+
console.error(`\nš„ UNHANDLED REJECTION #${uncaught_exceptions.length}:`);
|
|
46
|
+
console.error(`š Context: ${current_test_context}`);
|
|
47
|
+
console.error(`š„ Reason: ${reason}`);
|
|
48
|
+
console.error(`š Stack: ${reason?.stack || 'No stack available'}`);
|
|
49
|
+
console.error(`ā° Time: ${exception_info.timestamp}\n`);
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Runs a specific test file or pattern with enhanced debugging.
|
|
54
|
+
* @param {string} test_pattern - Test pattern to run
|
|
55
|
+
* @param {string} context_name - Context name for tracking
|
|
56
|
+
* @returns {Promise<number>} Exit code
|
|
57
|
+
*/
|
|
58
|
+
const run_debug_test = (test_pattern, context_name) => {
|
|
59
|
+
return new Promise((resolve) => {
|
|
60
|
+
current_test_context = context_name;
|
|
61
|
+
|
|
62
|
+
console.log(`\nš Debug Test: ${context_name}`);
|
|
63
|
+
console.log(`š Pattern: ${test_pattern}`);
|
|
64
|
+
console.log(`ā° Started: ${new Date().toISOString()}`);
|
|
65
|
+
|
|
66
|
+
const command = './node_modules/.bin/ava';
|
|
67
|
+
const args = ['--serial', '--verbose', test_pattern];
|
|
68
|
+
|
|
69
|
+
const child = spawn(command, args, {
|
|
70
|
+
stdio: 'inherit',
|
|
71
|
+
env: {
|
|
72
|
+
...process.env,
|
|
73
|
+
NODE_ENV: 'test',
|
|
74
|
+
NODE_OPTIONS: '--expose-gc --max-old-space-size=4096'
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
child.on('close', (code) => {
|
|
79
|
+
console.log(`\nā
Debug test completed: ${context_name} (exit code: ${code})`);
|
|
80
|
+
console.log(`š Exceptions during this test: ${uncaught_exceptions.filter(e => e.context === context_name).length}`);
|
|
81
|
+
resolve(code);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
child.on('error', (error) => {
|
|
85
|
+
console.error(`\nā Debug test failed: ${context_name} - ${error.message}`);
|
|
86
|
+
resolve(1);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Runs tests in isolated groups to identify exception sources.
|
|
93
|
+
*/
|
|
94
|
+
const run_isolated_debug_tests = async () => {
|
|
95
|
+
console.log('š Starting isolated debug test analysis...\n');
|
|
96
|
+
|
|
97
|
+
const test_groups = [
|
|
98
|
+
{
|
|
99
|
+
name: 'client-tests',
|
|
100
|
+
pattern: 'tests/client/**/*.test.js',
|
|
101
|
+
description: 'Client-side tests'
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
name: 'server-basic-tests',
|
|
105
|
+
pattern: 'tests/server/index.test.js',
|
|
106
|
+
description: 'Basic server tests'
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
name: 'server-lib-tests',
|
|
110
|
+
pattern: 'tests/server/lib/**/*.test.js',
|
|
111
|
+
description: 'Server library tests (including sync system)'
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: 'server-integration-tests',
|
|
115
|
+
pattern: 'tests/server/integration/**/*.test.js',
|
|
116
|
+
description: 'Server integration tests'
|
|
117
|
+
},
|
|
118
|
+
{
|
|
119
|
+
name: 'sync-system-only',
|
|
120
|
+
pattern: 'tests/server/lib/simple_sync_system.test.js',
|
|
121
|
+
description: 'Sync system tests only'
|
|
122
|
+
}
|
|
123
|
+
];
|
|
124
|
+
|
|
125
|
+
for (const group of test_groups) {
|
|
126
|
+
if (existsSync(group.pattern.replace('**/*.test.js', '').replace('*.test.js', ''))) {
|
|
127
|
+
await run_debug_test(group.pattern, group.name);
|
|
128
|
+
|
|
129
|
+
// Wait between test groups
|
|
130
|
+
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
131
|
+
} else {
|
|
132
|
+
console.log(`ā ļø Skipping ${group.name} - path not found`);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Summary report
|
|
137
|
+
console.log('\nš EXCEPTION ANALYSIS SUMMARY:');
|
|
138
|
+
console.log(`Total uncaught exceptions detected: ${uncaught_exceptions.length}`);
|
|
139
|
+
|
|
140
|
+
if (uncaught_exceptions.length > 0) {
|
|
141
|
+
console.log('\nš„ Exception breakdown by test context:');
|
|
142
|
+
const by_context = {};
|
|
143
|
+
|
|
144
|
+
uncaught_exceptions.forEach(exc => {
|
|
145
|
+
by_context[exc.context] = (by_context[exc.context] || 0) + 1;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
Object.entries(by_context).forEach(([context, count]) => {
|
|
149
|
+
console.log(` ${context}: ${count} exceptions`);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
console.log('\nš Detailed exception information:');
|
|
153
|
+
uncaught_exceptions.forEach((exc, index) => {
|
|
154
|
+
console.log(`\nException #${index + 1}:`);
|
|
155
|
+
console.log(` Type: ${exc.type}`);
|
|
156
|
+
console.log(` Context: ${exc.context}`);
|
|
157
|
+
console.log(` Message: ${exc.message}`);
|
|
158
|
+
console.log(` Time: ${exc.timestamp}`);
|
|
159
|
+
if (exc.stack) {
|
|
160
|
+
console.log(` Stack (first 3 lines):`);
|
|
161
|
+
const stack_lines = exc.stack.split('\n').slice(0, 3);
|
|
162
|
+
stack_lines.forEach(line => console.log(` ${line}`));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
} else {
|
|
166
|
+
console.log('š No uncaught exceptions detected in isolated tests!');
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Main execution function.
|
|
172
|
+
*/
|
|
173
|
+
const main = async () => {
|
|
174
|
+
const args = process.argv.slice(2);
|
|
175
|
+
|
|
176
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
177
|
+
console.log(`
|
|
178
|
+
š Debug Test Runner for JoystickDB
|
|
179
|
+
|
|
180
|
+
Usage: node debug_test_runner.js [isolated]
|
|
181
|
+
|
|
182
|
+
Commands:
|
|
183
|
+
isolated - Run tests in isolated groups to identify exception sources
|
|
184
|
+
|
|
185
|
+
This runner captures uncaught exceptions and unhandled rejections
|
|
186
|
+
without terminating the process, allowing us to identify which
|
|
187
|
+
specific tests are causing problems.
|
|
188
|
+
`);
|
|
189
|
+
process.exit(0);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
if (args[0] === 'isolated' || args.length === 0) {
|
|
193
|
+
await run_isolated_debug_tests();
|
|
194
|
+
} else {
|
|
195
|
+
console.error('ā Unknown command. Use --help for usage information.');
|
|
196
|
+
process.exit(1);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
console.log('\nš Debug analysis complete.');
|
|
200
|
+
process.exit(0);
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
// Run the main function
|
|
204
|
+
main().catch(error => {
|
|
205
|
+
console.error(`\nš„ Debug runner error: ${error.message}`);
|
|
206
|
+
console.error(error.stack);
|
|
207
|
+
process.exit(1);
|
|
208
|
+
});
|
package/dist/client/index.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
import h from"net";import{EventEmitter as l}from"events";import{encode as m,decode as p}from"msgpackr";import f from"./database.js";const _=()=>({useFloat32:!1,int64AsType:"number",mapsAsObjects:!0}),g=s=>{const e=m(s,_()),t=Buffer.allocUnsafe(4);return t.writeUInt32BE(e.length,0),Buffer.concat([t,e])},q=(s,e)=>{const t=s.slice(0,e),n=s.slice(e);try{return{message:p(t,_()),buffer:n}}catch(i){throw new Error(`Invalid message format: ${i.message}`)}},y=s=>{if(s.length<4)return{expected_length:null,buffer:s};const e=s.readUInt32BE(0),t=s.slice(4);return{expected_length:e,buffer:t}},b=()=>{let s=Buffer.alloc(0),e=null;return{parse_messages:i=>{s=Buffer.concat([s,i]);const r=[];for(;s.length>0;){if(e===null){const
|
|
1
|
+
import h from"net";import{EventEmitter as l}from"events";import{encode as m,decode as p}from"msgpackr";import f from"./database.js";const _=()=>({useFloat32:!1,int64AsType:"number",mapsAsObjects:!0}),g=s=>{const e=m(s,_()),t=Buffer.allocUnsafe(4);return t.writeUInt32BE(e.length,0),Buffer.concat([t,e])},q=(s,e)=>{const t=s.slice(0,e),n=s.slice(e);try{return{message:p(t,_()),buffer:n}}catch(i){throw new Error(`Invalid message format: ${i.message}`)}},y=s=>{if(s.length<4)return{expected_length:null,buffer:s};const e=s.readUInt32BE(0),t=s.slice(4);return{expected_length:e,buffer:t}},b=()=>{let s=Buffer.alloc(0),e=null;return{parse_messages:i=>{s=Buffer.concat([s,i]);const r=[];for(;s.length>0;){if(e===null){const c=y(s);if(e=c.expected_length,s=c.buffer,e===null)break}if(s.length<e)break;const a=q(s,e);r.push(a.message),s=a.buffer,e=null}return r},reset:()=>{s=Buffer.alloc(0),e=null}}},w=(s,e)=>Math.min(e*Math.pow(2,s-1),3e4),k=(s={})=>({host:s.host||"localhost",port:s.port||1983,password:s.password||null,timeout:s.timeout||5e3,reconnect:s.reconnect!==!1,max_reconnect_attempts:s.max_reconnect_attempts||10,reconnect_delay:s.reconnect_delay||1e3,auto_connect:s.auto_connect!==!1}),x=(s,e,t)=>setTimeout(()=>{s&&!s.destroyed&&(s.destroy(),e(new Error("Connection timeout")))},t),E=(s,e,t)=>setTimeout(()=>{const n=s.get(e);n&&(s.delete(e),n.reject(new Error("Request timeout")))},t),u=(s,e)=>{for(const[t,{reject:n,timeout:i}]of s)clearTimeout(i),n(new Error(e));s.clear()},T=s=>s.ok===1||s.ok===!0,v=s=>s.ok===0||s.ok===!1,B=s=>typeof s.error=="string"?s.error:JSON.stringify(s.error)||"Operation failed";class d extends l{constructor(e={}){super();const t=k(e);this.host=t.host,this.port=t.port,this.password=t.password,this.timeout=t.timeout,this.reconnect=t.reconnect,this.max_reconnect_attempts=t.max_reconnect_attempts,this.reconnect_delay=t.reconnect_delay,this.socket=null,this.message_parser=null,this.is_connected=!1,this.is_authenticated=!1,this.is_connecting=!1,this.reconnect_attempts=0,this.reconnect_timeout=null,this.pending_requests=new Map,this.request_id_counter=0,this.request_queue=[],t.auto_connect&&this.connect()}connect(){if(this.is_connecting||this.is_connected)return;this.is_connecting=!0,this.socket=new h.Socket,this.message_parser=b();const e=x(this.socket,this.handle_connection_error.bind(this),this.timeout);this.setup_socket_handlers(e),this.socket.connect(this.port,this.host,()=>{this.handle_successful_connection(e)})}setup_socket_handlers(e){this.socket.on("data",t=>{this.handle_incoming_data(t)}),this.socket.on("error",t=>{clearTimeout(e),this.handle_connection_error(t)}),this.socket.on("close",()=>{clearTimeout(e),this.handle_disconnect()})}handle_successful_connection(e){clearTimeout(e),this.is_connected=!0,this.is_connecting=!1,this.reconnect_attempts=0,this.emit("connect"),this.password?this.authenticate():this.handle_authentication_complete()}handle_authentication_complete(){this.is_authenticated=!0,this.emit("authenticated"),this.process_request_queue()}handle_incoming_data(e){try{const t=this.message_parser.parse_messages(e);for(const n of t)this.handle_message(n)}catch(t){this.emit("error",new Error(`Message parsing failed: ${t.message}`))}}async authenticate(){if(!this.password){this.emit("error",new Error('Password required for authentication. Provide password in client options: joystickdb.client({ password: "your_password" })')),this.disconnect();return}try{if((await this.send_request("authentication",{password:this.password})).ok===1)this.handle_authentication_complete();else throw new Error("Authentication failed")}catch(e){this.emit("error",new Error(`Authentication error: ${e.message}`)),this.disconnect()}}handle_message(e){this.pending_requests.size>0?this.handle_pending_request_response(e):this.emit("response",e)}handle_pending_request_response(e){const[t,{resolve:n,reject:i,timeout:r}]=this.pending_requests.entries().next().value;if(clearTimeout(r),this.pending_requests.delete(t),T(e))n(e);else if(v(e)){const a=B(e);i(new Error(a))}else n(e)}handle_connection_error(e){this.reset_connection_state(),u(this.pending_requests,"Connection lost"),this.emit("error",e),this.should_attempt_reconnect()?this.schedule_reconnect():this.emit("disconnect")}handle_disconnect(){this.reset_connection_state(),u(this.pending_requests,"Connection closed"),this.should_attempt_reconnect()?this.schedule_reconnect():this.emit("disconnect")}reset_connection_state(){this.is_connecting=!1,this.is_connected=!1,this.is_authenticated=!1,this.socket&&(this.socket.removeAllListeners(),this.socket.destroy(),this.socket=null),this.message_parser&&this.message_parser.reset()}should_attempt_reconnect(){return this.reconnect&&this.reconnect_attempts<this.max_reconnect_attempts}schedule_reconnect(){this.reconnect_attempts++;const e=w(this.reconnect_attempts,this.reconnect_delay);this.emit("reconnecting",{attempt:this.reconnect_attempts,delay:e}),this.reconnect_timeout=setTimeout(()=>{this.connect()},e)}send_request(e,t={},n=!0){return new Promise((i,r)=>{const a=++this.request_id_counter,o={message:{op:e,data:t},resolve:i,reject:r,request_id:a};if(this.should_queue_request(e,n)){this.request_queue.push(o);return}this.send_request_now(o)})}should_queue_request(e,t){const i=!["authentication","setup","ping"].includes(e);return(!this.is_connected||i&&!this.is_authenticated)&&t}send_request_now(e){const{message:t,resolve:n,reject:i,request_id:r}=e,a=E(this.pending_requests,r,this.timeout);this.pending_requests.set(r,{resolve:n,reject:i,timeout:a});try{const c=g(t);this.socket.write(c)}catch(c){clearTimeout(a),this.pending_requests.delete(r),i(c)}}process_request_queue(){for(;this.request_queue.length>0&&this.is_connected&&this.is_authenticated;){const e=this.request_queue.shift();this.send_request_now(e)}}disconnect(){this.reconnect=!1,this.reconnect_timeout&&(clearTimeout(this.reconnect_timeout),this.reconnect_timeout=null),this.socket&&this.socket.end()}async backup_now(){return this.send_request("admin",{admin_action:"backup_now"})}async list_backups(){return this.send_request("admin",{admin_action:"list_backups"})}async restore_backup(e){return this.send_request("admin",{admin_action:"restore_backup",backup_name:e})}async get_replication_status(){return this.send_request("admin",{admin_action:"get_replication_status"})}async add_secondary(e){return this.send_request("admin",{admin_action:"add_secondary",...e})}async remove_secondary(e){return this.send_request("admin",{admin_action:"remove_secondary",secondary_id:e})}async sync_secondaries(){return this.send_request("admin",{admin_action:"sync_secondaries"})}async get_secondary_health(){return this.send_request("admin",{admin_action:"get_secondary_health"})}async get_forwarder_status(){return this.send_request("admin",{admin_action:"get_forwarder_status"})}async ping(){return this.send_request("ping",{},!1)}async reload(){return this.send_request("reload")}async get_auto_index_stats(){return this.send_request("admin",{admin_action:"get_auto_index_stats"})}async setup(){const e=await this.send_request("setup",{},!1);return e.data&&e.data.instructions&&console.log(e.data.instructions),e}async delete_many(e,t={},n={}){return this.send_request("delete_many",{database:"default",collection:e,filter:t,options:n})}db(e){return new f(this,e)}async list_databases(){return this.send_request("admin",{admin_action:"list_databases"})}async get_stats(){return this.send_request("admin",{admin_action:"stats"})}}class j{constructor(e,t,n){this.client=e,this.database_name=t,this.collection_name=n}async insert_one(e,t={}){return this.client.send_request("insert_one",{database:this.database_name,collection:this.collection_name,document:e,options:t})}async find_one(e={},t={}){return(await this.client.send_request("find_one",{database:this.database_name,collection:this.collection_name,filter:e,options:t})).document}async find(e={},t={}){return(await this.client.send_request("find",{database:this.database_name,collection:this.collection_name,filter:e,options:t})).documents||[]}async count_documents(e={},t={}){return(await this.client.send_request("count_documents",{database:this.database_name,collection:this.collection_name,filter:e,options:t})).count}async update_one(e,t,n={}){return this.client.send_request("update_one",{database:this.database_name,collection:this.collection_name,filter:e,update:t,options:n})}async delete_one(e,t={}){return this.client.send_request("delete_one",{database:this.database_name,collection:this.collection_name,filter:e,options:t})}async delete_many(e={},t={}){return this.client.send_request("delete_many",{database:this.database_name,collection:this.collection_name,filter:e,options:t})}async bulk_write(e,t={}){return this.client.send_request("bulk_write",{database:this.database_name,collection:this.collection_name,operations:e,options:t})}async create_index(e,t={}){return this.client.send_request("create_index",{database:this.database_name,collection:this.collection_name,field:e,options:t})}async upsert_index(e,t={}){return this.client.send_request("create_index",{database:this.database_name,collection:this.collection_name,field:e,options:{...t,upsert:!0}})}async drop_index(e){return this.client.send_request("drop_index",{database:this.database_name,collection:this.collection_name,field:e})}async get_indexes(){return this.client.send_request("get_indexes",{database:this.database_name,collection:this.collection_name})}}d.Collection=j;const C={client:s=>new d(s)};var P=C;export{P as default};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import n from"cluster";import h from"os";import{EventEmitter as d}from"events";import{writeFileSync as
|
|
1
|
+
import n from"cluster";import h from"os";import{EventEmitter as d}from"events";import{writeFileSync as p}from"fs";import{load_settings as l,get_settings as u,get_port_configuration as c}from"../lib/load_settings.js";import{restore_backup as w,start_backup_schedule as g,stop_backup_schedule as f}from"../lib/backup_manager.js";import m from"../lib/logger.js";import{initialize_database as k,check_and_grow_map_size as y,cleanup_database as b}from"../lib/query_engine.js";import{setup_authentication as v,verify_password as q,initialize_auth_manager as D}from"../lib/auth_manager.js";import x from"../lib/operations/insert_one.js";import E from"../lib/operations/update_one.js";import T from"../lib/operations/delete_one.js";import S from"../lib/operations/delete_many.js";import j from"../lib/operations/bulk_write.js";import z from"../lib/operations/find_one.js";import W from"../lib/operations/find.js";import F from"../lib/operations/count_documents.js";import N from"../lib/operations/create_index.js";import P from"../lib/operations/drop_index.js";import O from"../lib/operations/get_indexes.js";import{start_http_server as R,stop_http_server as $,is_setup_required as A}from"../lib/http_server.js";import{is_development_mode as I,display_development_startup_message as L}from"../lib/development_mode.js";import{initialize_api_key_manager as M}from"../lib/api_key_manager.js";class H extends d{constructor(e={}){super(),this.workers=new Map,this.write_queue=[],this.processing_writes=!1,this.authenticated_sessions=new Map,this.worker_count=e.worker_count||h.cpus().length,this.port=e.port||1983,this.settings_file=e.settings_file||"settings.db.json",this.settings=null,this.pending_writes=new Map,this.write_id_counter=0,this.shutting_down=!1,this.master_id=`master_${Date.now()}_${Math.random()}`;const{create_context_logger:t}=m("master");this.log=t({port:this.port,worker_count:this.worker_count,master_id:this.master_id}),this.setup_master()}setup_master(){n.setupPrimary({exec:new URL("./index.js",import.meta.url).pathname,args:[],silent:!1}),n.on("exit",(e,t,s)=>{this.log.warn("Worker died",{worker_pid:e.process.pid,exit_code:t,signal:s}),this.handle_worker_death(e)}),n.on("message",(e,t)=>{this.handle_worker_message(e,t)})}get_database_path(){let e;try{const t=u();if(t?.data_path)e=t.data_path;else{const{tcp_port:s}=c();e=`./.joystick/data/joystickdb_${s}`}}catch{const{tcp_port:s}=c();e=`./.joystick/data/joystickdb_${s}`}return e}async initialize_core_systems(){const e=this.get_database_path();k(e),D(),await M(),this.log.info("Database and auth manager initialized")}async handle_startup_restore(){if(this.settings?.restore_from)try{this.log.info("Startup restore requested",{backup_filename:this.settings.restore_from});const e=await w(this.settings.restore_from);this.log.info("Startup restore completed",{backup_filename:this.settings.restore_from,duration_ms:e.duration_ms}),this.remove_restore_from_settings()}catch(e){this.log.error("Startup restore failed",{backup_filename:this.settings.restore_from,error:e.message}),this.log.info("Continuing with existing database after restore failure")}}remove_restore_from_settings(){const e={...this.settings};delete e.restore_from,p(this.settings_file,JSON.stringify(e,null,2)),this.settings=l(this.settings_file),this.log.info("Removed restore_from from settings after successful restore")}start_backup_scheduling(){if(this.settings?.s3)try{g(),this.log.info("Backup scheduling started")}catch(e){this.log.warn("Failed to start backup scheduling",{error:e.message})}}async start_setup_server(){if(A())try{const{http_port:e}=c();await R(e)&&this.log.info("HTTP setup server started",{http_port:e})}catch(e){this.log.warn("Failed to start HTTP setup server",{error:e.message})}}spawn_all_workers(){for(let e=0;e<this.worker_count;e++)this.spawn_worker()}display_development_message(){if(I()){const{tcp_port:e,http_port:t}=c();L(e,t)}}async start(){const e=Date.now();try{this.settings=l(this.settings_file),this.log.info("Settings loaded successfully",{settings_file:this.settings_file}),await this.initialize_core_systems(),await this.handle_startup_restore(),this.start_backup_scheduling(),await this.start_setup_server(),this.spawn_all_workers(),this.display_development_message();const t=Date.now()-e;this.log.info("Master process started successfully",{workers_spawned:this.worker_count,startup_duration_ms:t})}catch(t){this.log.error("Failed to start master process",{error:t.message}),process.exit(1)}}spawn_worker(){const e=Date.now();this.log.info("Spawning worker");const t=n.fork({WORKER_PORT:this.port,WORKER_SETTINGS:JSON.stringify(this.settings)});this.workers.set(t.id,{worker:t,connections:0,last_heartbeat:Date.now(),status:"starting"});const s=Date.now()-e;return this.log.info("Worker spawned successfully",{worker_id:t.id,worker_pid:t.process.pid,spawn_duration_ms:s}),t}handle_worker_death(e){this.workers.delete(e.id),this.shutting_down||(this.log.info("Respawning worker after death",{dead_worker_id:e.id,respawn_delay_ms:1e3}),setTimeout(()=>{this.spawn_worker()},1e3))}handle_worker_message(e,t){switch(t.type){case"worker_ready":this.handle_worker_ready_for_config(e,t);break;case"server_ready":this.handle_worker_server_ready(e,t);break;case"write_request":this.handle_write_request(e,t);break;case"auth_request":this.handle_auth_request(e,t);break;case"setup_request":this.handle_setup_request(e,t);break;case"connection_count":this.update_worker_connections(e,t);break;case"heartbeat":this.handle_worker_heartbeat(e,t);break;default:this.log.warn("Unknown message type received",{message_type:t.type,worker_id:e.id})}}handle_worker_ready_for_config(e,t){this.log.info("Worker ready for config, sending configuration",{worker_id:e.id,worker_pid:e.process.pid,master_id:this.master_id}),e.send({type:"config",data:{port:this.port,settings:this.settings,master_id:this.master_id}})}handle_worker_server_ready(e,t){const s=this.workers.get(e.id);s&&(s.status="ready",this.log.info("Worker server ready",{worker_id:e.id,worker_pid:e.process.pid}))}async handle_write_request(e,t){if(this.shutting_down){e.send({type:"write_response",data:{write_id:t.data.write_id,success:!1,error:"Server is shutting down"}});return}const{write_id:s,op_type:r,data:i,socket_id:o}=t.data;try{const a={write_id:s,worker_id:e.id,op_type:r,data:i,socket_id:o,timestamp:Date.now()};this.write_queue.push(a),this.process_write_queue()}catch(a){e.send({type:"write_response",data:{write_id:s,success:!1,error:a.message}})}}async process_write_queue(){if(!(this.processing_writes||this.write_queue.length===0)){for(this.processing_writes=!0;this.write_queue.length>0;){const e=this.write_queue.shift();await this.execute_write_operation(e)}this.processing_writes=!1,this.shutting_down&&this.write_queue.length===0&&this.emit("writes_completed")}}async execute_write_operation(e){const{write_id:t,worker_id:s,op_type:r,data:i,socket_id:o}=e,a=this.workers.get(s);if(!a){this.log.error("Worker not found for write operation",{worker_id:s});return}try{const _=await this.perform_database_operation(r,i);a.worker.send({type:"write_response",data:{write_id:t,success:!0,result:_}}),this.broadcast_write_notification(r,i,s)}catch(_){this.log.error("Write operation failed",{write_id:t,op_type:r,worker_id:s,error_message:_.message}),a.worker.send({type:"write_response",data:{write_id:t,success:!1,error:_.message}})}}async perform_database_operation(e,t){const s=Date.now();this.log.info("Executing database operation",{op_type:e});try{let r;const i=t.database||"default";switch(e){case"insert_one":r=await x(i,t.collection,t.document,t.options);break;case"update_one":r=await E(i,t.collection,t.filter,t.update,t.options);break;case"delete_one":r=await T(i,t.collection,t.filter,t.options);break;case"delete_many":r=await S(i,t.collection,t.filter,t.options);break;case"bulk_write":r=await j(i,t.collection,t.operations,t.options);break;case"find_one":r=await z(i,t.collection,t.filter,t.options);break;case"find":r=await W(i,t.collection,t.filter,t.options);break;case"count_documents":r=await F(i,t.collection,t.filter,t.options);break;case"create_index":r=await N(i,t.collection,t.field,t.options);break;case"drop_index":r=await P(i,t.collection,t.field);break;case"get_indexes":r=await O(i,t.collection);break;default:throw new Error(`Unsupported database operation: ${e}`)}const o=Date.now()-s;return this.log.log_operation(e,o,{result:r}),["find_one","find","count_documents","get_indexes"].includes(e)||setImmediate(()=>y()),r}catch(r){const i=Date.now()-s;throw this.log.error("Database operation failed",{op_type:e,duration_ms:i,error_message:r.message}),r}}broadcast_write_notification(e,t,s){const r={type:"write_notification",data:{op_type:e,data:t,timestamp:Date.now()}};for(const[i,o]of this.workers)i!==s&&o.status==="ready"&&o.worker.send(r)}async handle_auth_request(e,t){const{auth_id:s,socket_id:r,password:i}=t.data;try{const o=await q(i,"cluster_client");o&&this.authenticated_sessions.set(r,{authenticated_at:Date.now(),worker_id:e.id}),e.send({type:"auth_response",data:{auth_id:s,success:o,message:o?"Authentication successful":"Authentication failed"}})}catch(o){e.send({type:"auth_response",data:{auth_id:s,success:!1,message:`Authentication error: ${o.message}`}})}}handle_setup_request(e,t){const{setup_id:s,socket_id:r}=t.data;try{const i=v(),o=`===
|
|
2
2
|
JoystickDB Setup
|
|
3
3
|
|
|
4
4
|
Your database has been setup. Follow the instructions below carefully to avoid issues.
|
|
@@ -17,4 +17,4 @@ const client = joystickdb.client({
|
|
|
17
17
|
});
|
|
18
18
|
|
|
19
19
|
await client.ping();
|
|
20
|
-
===`;e.send({type:"setup_response",data:{setup_id:s,success:!0,password:i,instructions:o,message:"Authentication setup completed successfully"}})}catch(i){e.send({type:"setup_response",data:{setup_id:s,success:!1,error:i.message}})}}update_worker_connections(e,t){const s=this.workers.get(e.id);s&&(s.connections=t.data.count)}handle_worker_heartbeat(e,t){const s=this.workers.get(e.id);s&&(s.last_heartbeat=Date.now())}get_cluster_stats(){const e={master_pid:process.pid,worker_count:this.workers.size,total_connections:0,write_queue_length:this.write_queue.length,authenticated_sessions:this.authenticated_sessions.size,workers:[]};for(const[t,s]of this.workers)e.total_connections+=s.connections,e.workers.push({id:t,pid:s.worker.process.pid,connections:s.connections,status:s.status,last_heartbeat:s.last_heartbeat});return e}async stop_http_server_gracefully(){try{await
|
|
20
|
+
===`;e.send({type:"setup_response",data:{setup_id:s,success:!0,password:i,instructions:o,message:"Authentication setup completed successfully"}})}catch(i){e.send({type:"setup_response",data:{setup_id:s,success:!1,error:i.message}})}}update_worker_connections(e,t){const s=this.workers.get(e.id);s&&(s.connections=t.data.count)}handle_worker_heartbeat(e,t){const s=this.workers.get(e.id);s&&(s.last_heartbeat=Date.now())}get_cluster_stats(){const e={master_pid:process.pid,worker_count:this.workers.size,total_connections:0,write_queue_length:this.write_queue.length,authenticated_sessions:this.authenticated_sessions.size,workers:[]};for(const[t,s]of this.workers)e.total_connections+=s.connections,e.workers.push({id:t,pid:s.worker.process.pid,connections:s.connections,status:s.status,last_heartbeat:s.last_heartbeat});return e}async stop_http_server_gracefully(){try{await $(),this.log.info("HTTP server stopped")}catch(e){this.log.warn("Failed to stop HTTP server",{error:e.message})}}stop_backup_scheduling_gracefully(){try{f(),this.log.info("Backup scheduling stopped")}catch(e){this.log.warn("Failed to stop backup scheduling",{error:e.message})}}send_shutdown_signals(){for(const[e,t]of this.workers)try{t.worker.send({type:"shutdown"})}catch(s){this.log.warn("Error sending shutdown signal to worker",{worker_id:e,error:s.message})}}async wait_for_pending_writes(){this.write_queue.length!==0&&(this.log.info("Waiting for pending writes to complete",{pending_writes:this.write_queue.length}),await new Promise(e=>{const t=setTimeout(()=>{this.log.warn("Timeout waiting for writes to complete, proceeding with shutdown"),e()},process.env.NODE_ENV==="test"?1e3:5e3);this.once("writes_completed",()=>{clearTimeout(t),e()})}))}disconnect_all_workers(){for(const[e,t]of this.workers)try{t.worker.disconnect()}catch(s){this.log.warn("Error disconnecting worker",{worker_id:e,error:s.message})}}force_kill_remaining_workers(){for(const[e,t]of this.workers){this.log.warn("Force killing worker after timeout",{worker_id:e});try{t.worker.kill("SIGKILL")}catch(s){this.log.warn("Error force killing worker",{worker_id:e,error:s.message})}}this.workers.clear()}async wait_for_workers_to_exit(){const e=process.env.NODE_ENV==="test"?500:3e3;await new Promise(t=>{const s=setTimeout(()=>{this.force_kill_remaining_workers(),t()},e),r=()=>{this.workers.size===0?(clearTimeout(s),t()):setTimeout(r,50)};r()})}cleanup_database_connections(){try{b(),this.log.info("Database cleanup completed")}catch(e){this.log.warn("Error during database cleanup",{error:e.message})}}clear_internal_state(){this.authenticated_sessions.clear(),this.write_queue.length=0,this.pending_writes.clear()}perform_test_environment_cleanup(){if(process.env.NODE_ENV==="test")try{for(const e in n.workers){const t=n.workers[e];if(t&&!t.isDead()){this.log.info("Force killing remaining cluster worker",{worker_id:e,worker_pid:t.process.pid});try{t.kill("SIGKILL")}catch(s){this.log.warn("Error force killing remaining worker",{worker_id:e,error:s.message})}}}for(const e in n.workers)delete n.workers[e];n.removeAllListeners(),this.log.info("Aggressive cluster cleanup completed for test environment")}catch(e){this.log.warn("Error during aggressive cluster cleanup",{error:e.message})}}async shutdown(){const e=Date.now();this.log.info("Initiating graceful shutdown"),this.shutting_down=!0,await this.stop_http_server_gracefully(),this.stop_backup_scheduling_gracefully(),this.send_shutdown_signals(),await this.wait_for_pending_writes(),this.log.info("All writes completed, disconnecting workers"),this.disconnect_all_workers(),await this.wait_for_workers_to_exit(),this.cleanup_database_connections(),this.clear_internal_state(),this.perform_test_environment_cleanup();const t=Date.now()-e;this.log.info("Shutdown complete",{shutdown_duration_ms:t})}}var ue=H;export{ue as default};
|