@joystick.js/db-canary 0.0.0-canary.2250 → 0.0.0-canary.2252
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/client/database.js +1 -1
- package/dist/client/index.js +1 -1
- package/dist/server/cluster/master.js +4 -4
- 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/backup_manager.js +1 -1
- package/dist/server/lib/index_manager.js +1 -1
- package/dist/server/lib/operation_dispatcher.js +1 -1
- package/dist/server/lib/operations/admin.js +1 -1
- package/dist/server/lib/operations/bulk_write.js +1 -1
- package/dist/server/lib/operations/create_index.js +1 -1
- package/dist/server/lib/operations/delete_many.js +1 -1
- package/dist/server/lib/operations/delete_one.js +1 -1
- package/dist/server/lib/operations/find.js +1 -1
- package/dist/server/lib/operations/find_one.js +1 -1
- package/dist/server/lib/operations/insert_one.js +1 -1
- package/dist/server/lib/operations/update_one.js +1 -1
- package/dist/server/lib/send_response.js +1 -1
- package/dist/server/lib/tcp_protocol.js +1 -1
- package/package.json +2 -2
- package/src/client/database.js +92 -119
- package/src/client/index.js +279 -345
- package/src/server/cluster/master.js +265 -156
- package/src/server/cluster/worker.js +26 -18
- package/src/server/index.js +553 -330
- package/src/server/lib/auto_index_manager.js +85 -23
- package/src/server/lib/backup_manager.js +117 -70
- package/src/server/lib/index_manager.js +63 -25
- package/src/server/lib/operation_dispatcher.js +339 -168
- package/src/server/lib/operations/admin.js +343 -205
- package/src/server/lib/operations/bulk_write.js +458 -194
- package/src/server/lib/operations/create_index.js +127 -34
- package/src/server/lib/operations/delete_many.js +204 -67
- package/src/server/lib/operations/delete_one.js +164 -52
- package/src/server/lib/operations/find.js +563 -201
- package/src/server/lib/operations/find_one.js +544 -188
- package/src/server/lib/operations/insert_one.js +147 -52
- package/src/server/lib/operations/update_one.js +334 -93
- package/src/server/lib/send_response.js +37 -17
- package/src/server/lib/tcp_protocol.js +158 -53
- package/tests/server/cluster/master_read_write_operations.test.js +5 -14
- package/tests/server/integration/authentication_integration.test.js +18 -10
- package/tests/server/integration/backup_integration.test.js +35 -27
- package/tests/server/lib/api_key_manager.test.js +88 -32
- package/tests/server/lib/development_mode.test.js +2 -2
- package/tests/server/lib/operations/admin.test.js +20 -12
- package/tests/server/lib/operations/delete_one.test.js +10 -4
- package/tests/server/lib/operations/find_array_queries.test.js +261 -0
package/src/server/index.js
CHANGED
|
@@ -56,25 +56,70 @@ import {
|
|
|
56
56
|
initialize_recovery_manager,
|
|
57
57
|
reset_recovery_state
|
|
58
58
|
} from './lib/recovery_manager.js';
|
|
59
|
+
import { has_settings } from './lib/load_settings.js';
|
|
59
60
|
|
|
60
|
-
/** @type {Set<string>} Set of authenticated client socket IDs */
|
|
61
61
|
const authenticated_clients = new Set();
|
|
62
|
-
|
|
63
|
-
/** @type {Object|null} Connection manager instance */
|
|
64
62
|
let connection_manager = null;
|
|
65
63
|
|
|
64
|
+
/**
|
|
65
|
+
* Validates authentication request data.
|
|
66
|
+
* @param {Object} data - Authentication data
|
|
67
|
+
* @returns {boolean} True if data is valid
|
|
68
|
+
*/
|
|
69
|
+
const validate_authentication_data = (data) => {
|
|
70
|
+
return data && data.password;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Creates authentication error response.
|
|
75
|
+
* @param {string} error_message - Error message
|
|
76
|
+
* @returns {Object} Error response object
|
|
77
|
+
*/
|
|
78
|
+
const create_authentication_error_response = (error_message) => ({
|
|
79
|
+
ok: 0,
|
|
80
|
+
error: error_message
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Creates authentication success response.
|
|
85
|
+
* @returns {Object} Success response object
|
|
86
|
+
*/
|
|
87
|
+
const create_authentication_success_response = () => ({
|
|
88
|
+
ok: 1,
|
|
89
|
+
version: "1.0.0",
|
|
90
|
+
message: 'Authentication successful'
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Sends response and closes socket connection.
|
|
95
|
+
* @param {net.Socket} socket - Client socket
|
|
96
|
+
* @param {Object} response - Response object to send
|
|
97
|
+
*/
|
|
98
|
+
const send_response_and_close_socket = (socket, response) => {
|
|
99
|
+
const encoded_response = encode_message(response);
|
|
100
|
+
socket.write(encoded_response);
|
|
101
|
+
socket.end();
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Sends response to socket without closing.
|
|
106
|
+
* @param {net.Socket} socket - Client socket
|
|
107
|
+
* @param {Object} response - Response object to send
|
|
108
|
+
*/
|
|
109
|
+
const send_response_to_socket = (socket, response) => {
|
|
110
|
+
const encoded_response = encode_message(response);
|
|
111
|
+
socket.write(encoded_response);
|
|
112
|
+
};
|
|
113
|
+
|
|
66
114
|
/**
|
|
67
115
|
* Handles client authentication requests.
|
|
68
116
|
* @param {net.Socket} socket - Client socket connection
|
|
69
|
-
* @param {Object}
|
|
70
|
-
* @param {string} data.password - Client password for authentication
|
|
117
|
+
* @param {Object} data - Authentication data containing password
|
|
71
118
|
*/
|
|
72
119
|
export const authentication = async (socket, data = {}) => {
|
|
73
|
-
if (!data
|
|
74
|
-
const response =
|
|
75
|
-
|
|
76
|
-
socket.write(encoded_response);
|
|
77
|
-
socket.end();
|
|
120
|
+
if (!validate_authentication_data(data)) {
|
|
121
|
+
const response = create_authentication_error_response('Authentication operation requires password to be set in data.');
|
|
122
|
+
send_response_and_close_socket(socket, response);
|
|
78
123
|
return;
|
|
79
124
|
}
|
|
80
125
|
|
|
@@ -82,66 +127,68 @@ export const authentication = async (socket, data = {}) => {
|
|
|
82
127
|
const client_ip = get_client_ip(socket);
|
|
83
128
|
|
|
84
129
|
if (is_rate_limited(client_ip)) {
|
|
85
|
-
const response =
|
|
86
|
-
|
|
87
|
-
socket.write(encoded_response);
|
|
88
|
-
socket.end();
|
|
130
|
+
const response = create_authentication_error_response('Too many failed attempts. Please try again later.');
|
|
131
|
+
send_response_and_close_socket(socket, response);
|
|
89
132
|
return;
|
|
90
133
|
}
|
|
91
134
|
|
|
92
135
|
const is_valid = await verify_password(data.password, client_ip);
|
|
93
136
|
|
|
94
137
|
if (!is_valid) {
|
|
95
|
-
const response =
|
|
96
|
-
|
|
97
|
-
socket.write(encoded_response);
|
|
98
|
-
socket.end();
|
|
138
|
+
const response = create_authentication_error_response('Authentication failed');
|
|
139
|
+
send_response_and_close_socket(socket, response);
|
|
99
140
|
return;
|
|
100
141
|
}
|
|
101
142
|
|
|
102
143
|
authenticated_clients.add(socket.id);
|
|
103
|
-
const auth_response =
|
|
104
|
-
|
|
105
|
-
version: "1.0.0",
|
|
106
|
-
message: 'Authentication successful'
|
|
107
|
-
};
|
|
108
|
-
const encoded_auth_response = encode_message(auth_response);
|
|
109
|
-
socket.write(encoded_auth_response);
|
|
144
|
+
const auth_response = create_authentication_success_response();
|
|
145
|
+
send_response_to_socket(socket, auth_response);
|
|
110
146
|
} catch (error) {
|
|
111
|
-
const response =
|
|
112
|
-
|
|
113
|
-
socket.write(encoded_response);
|
|
114
|
-
socket.end();
|
|
147
|
+
const response = create_authentication_error_response(`Authentication error: ${error.message}`);
|
|
148
|
+
send_response_and_close_socket(socket, response);
|
|
115
149
|
}
|
|
116
150
|
};
|
|
117
151
|
|
|
152
|
+
/**
|
|
153
|
+
* Creates setup success response.
|
|
154
|
+
* @param {string} password - Generated password
|
|
155
|
+
* @returns {Object} Setup response object
|
|
156
|
+
*/
|
|
157
|
+
const create_setup_success_response = (password) => ({
|
|
158
|
+
ok: 1,
|
|
159
|
+
password,
|
|
160
|
+
message: 'Authentication setup completed successfully. Save this password - it will not be shown again.'
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Creates setup error response.
|
|
165
|
+
* @param {string} error_message - Error message
|
|
166
|
+
* @returns {Object} Error response object
|
|
167
|
+
*/
|
|
168
|
+
const create_setup_error_response = (error_message) => ({
|
|
169
|
+
ok: 0,
|
|
170
|
+
error: `Setup error: ${error_message}`
|
|
171
|
+
});
|
|
172
|
+
|
|
118
173
|
/**
|
|
119
174
|
* Handles initial server setup, generating authentication password.
|
|
120
175
|
* @param {net.Socket} socket - Client socket connection
|
|
121
|
-
* @param {Object}
|
|
176
|
+
* @param {Object} data - Setup data (currently unused)
|
|
122
177
|
*/
|
|
123
178
|
export const setup = async (socket, data = {}) => {
|
|
124
179
|
try {
|
|
125
180
|
const password = setup_authentication();
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
const response = {
|
|
129
|
-
ok: 1,
|
|
130
|
-
password,
|
|
131
|
-
message: 'Authentication setup completed successfully. Save this password - it will not be shown again.'
|
|
132
|
-
};
|
|
133
|
-
const encoded_response = encode_message(response);
|
|
134
|
-
socket.write(encoded_response);
|
|
181
|
+
const response = create_setup_success_response(password);
|
|
182
|
+
send_response_to_socket(socket, response);
|
|
135
183
|
} catch (error) {
|
|
136
|
-
const response =
|
|
137
|
-
|
|
138
|
-
socket.write(encoded_response);
|
|
184
|
+
const response = create_setup_error_response(error.message);
|
|
185
|
+
send_response_to_socket(socket, response);
|
|
139
186
|
}
|
|
140
187
|
};
|
|
141
188
|
|
|
142
189
|
/**
|
|
143
190
|
* Validates if an operation type is supported.
|
|
144
|
-
* @param {string}
|
|
191
|
+
* @param {string} op_type - Operation type to validate
|
|
145
192
|
* @returns {boolean} True if operation type is valid
|
|
146
193
|
* @throws {Error} When no operation type is provided
|
|
147
194
|
*/
|
|
@@ -153,6 +200,32 @@ export const check_op_type = (op_type = '') => {
|
|
|
153
200
|
return op_types.includes(op_type);
|
|
154
201
|
};
|
|
155
202
|
|
|
203
|
+
/**
|
|
204
|
+
* Parses string data as JSON.
|
|
205
|
+
* @param {string} string_data - String data to parse
|
|
206
|
+
* @returns {Object|null} Parsed JSON object or null
|
|
207
|
+
*/
|
|
208
|
+
const parse_string_data = (string_data) => {
|
|
209
|
+
return safe_json_parse(string_data);
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Parses Buffer data as MessagePack then JSON.
|
|
214
|
+
* @param {Buffer} buffer_data - Buffer data to parse
|
|
215
|
+
* @returns {Object|null} Parsed object or null
|
|
216
|
+
*/
|
|
217
|
+
const parse_buffer_data = (buffer_data) => {
|
|
218
|
+
try {
|
|
219
|
+
const decoded_messagepack = decode_messagepack(buffer_data);
|
|
220
|
+
if (typeof decoded_messagepack === 'string') {
|
|
221
|
+
return safe_json_parse(decoded_messagepack);
|
|
222
|
+
}
|
|
223
|
+
return decoded_messagepack;
|
|
224
|
+
} catch (error) {
|
|
225
|
+
return null;
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
|
|
156
229
|
/**
|
|
157
230
|
* Parses raw data from various formats (string, Buffer, MessagePack) into JavaScript objects.
|
|
158
231
|
* @param {string|Buffer|Object} raw_data - Raw data to parse
|
|
@@ -161,17 +234,10 @@ export const check_op_type = (op_type = '') => {
|
|
|
161
234
|
export const parse_data = (raw_data) => {
|
|
162
235
|
try {
|
|
163
236
|
if (typeof raw_data === 'string') {
|
|
164
|
-
|
|
165
|
-
return data_as_json;
|
|
237
|
+
return parse_string_data(raw_data);
|
|
166
238
|
} else if (Buffer.isBuffer(raw_data)) {
|
|
167
|
-
|
|
168
|
-
const decoded_messagepack = decode_messagepack(raw_data);
|
|
169
|
-
if (typeof decoded_messagepack === 'string') {
|
|
170
|
-
return safe_json_parse(decoded_messagepack);
|
|
171
|
-
}
|
|
172
|
-
return decoded_messagepack;
|
|
239
|
+
return parse_buffer_data(raw_data);
|
|
173
240
|
} else {
|
|
174
|
-
// NOTE: raw_data is already decoded from MessagePack by the message parser.
|
|
175
241
|
return raw_data;
|
|
176
242
|
}
|
|
177
243
|
} catch (error) {
|
|
@@ -186,7 +252,6 @@ export const parse_data = (raw_data) => {
|
|
|
186
252
|
* @returns {boolean} True if socket is authenticated or in development mode
|
|
187
253
|
*/
|
|
188
254
|
const check_authentication = (socket) => {
|
|
189
|
-
// NOTE: Bypass authentication in development mode for TCP connections.
|
|
190
255
|
if (is_development_mode()) {
|
|
191
256
|
return true;
|
|
192
257
|
}
|
|
@@ -194,363 +259,509 @@ const check_authentication = (socket) => {
|
|
|
194
259
|
return authenticated_clients.has(socket.id);
|
|
195
260
|
};
|
|
196
261
|
|
|
197
|
-
|
|
198
262
|
/**
|
|
199
|
-
*
|
|
200
|
-
* @
|
|
201
|
-
* @
|
|
202
|
-
* @property {string} [environment] - Environment name for settings file (defaults to NODE_ENV)
|
|
263
|
+
* Attempts to restore from backup if configured.
|
|
264
|
+
* @param {Object} settings - Server settings
|
|
265
|
+
* @param {Function} log - Logger function
|
|
203
266
|
*/
|
|
267
|
+
const attempt_startup_restore = async (settings, log) => {
|
|
268
|
+
if (!settings?.restore_from) {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
log.info('Startup restore requested', { backup_filename: settings.restore_from });
|
|
274
|
+
|
|
275
|
+
const restore_result = await restore_backup(settings.restore_from);
|
|
276
|
+
|
|
277
|
+
log.info('Startup restore completed', {
|
|
278
|
+
backup_filename: settings.restore_from,
|
|
279
|
+
duration_ms: restore_result.duration_ms
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
const updated_settings = { ...settings };
|
|
283
|
+
delete updated_settings.restore_from;
|
|
284
|
+
|
|
285
|
+
process.env.JOYSTICK_DB_SETTINGS = JSON.stringify(updated_settings);
|
|
286
|
+
|
|
287
|
+
load_settings();
|
|
288
|
+
|
|
289
|
+
log.info('Removed restore_from from settings after successful restore');
|
|
290
|
+
} catch (restore_error) {
|
|
291
|
+
log.error('Startup restore failed', {
|
|
292
|
+
backup_filename: settings.restore_from,
|
|
293
|
+
error: restore_error.message
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
log.info('Continuing with fresh database after restore failure');
|
|
297
|
+
}
|
|
298
|
+
};
|
|
204
299
|
|
|
205
300
|
/**
|
|
206
|
-
*
|
|
207
|
-
* @returns {
|
|
301
|
+
* Loads server settings with fallback for missing files.
|
|
302
|
+
* @returns {Object|null} Settings object or null if not available
|
|
208
303
|
*/
|
|
209
|
-
|
|
210
|
-
const { create_context_logger } = create_logger('server');
|
|
211
|
-
const log = create_context_logger();
|
|
212
|
-
|
|
213
|
-
/*
|
|
214
|
-
NOTE:
|
|
215
|
-
Try to load settings, but don't fail if file doesn't exist (for tests).
|
|
216
|
-
*/
|
|
217
|
-
let settings = null;
|
|
304
|
+
const load_server_settings = () => {
|
|
218
305
|
try {
|
|
219
306
|
load_settings();
|
|
220
|
-
|
|
307
|
+
return get_settings();
|
|
221
308
|
} catch (error) {
|
|
222
|
-
|
|
223
|
-
NOTE:
|
|
224
|
-
Settings file doesn't exist - this is OK for tests.
|
|
225
|
-
We'll handle missing settings in individual operations.
|
|
226
|
-
*/
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// NOTE: Handle startup restore if configured.
|
|
230
|
-
if (settings?.restore_from) {
|
|
231
|
-
try {
|
|
232
|
-
log.info('Startup restore requested', { backup_filename: settings.restore_from });
|
|
233
|
-
|
|
234
|
-
const restore_result = await restore_backup(settings.restore_from);
|
|
235
|
-
|
|
236
|
-
log.info('Startup restore completed', {
|
|
237
|
-
backup_filename: settings.restore_from,
|
|
238
|
-
duration_ms: restore_result.duration_ms
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
// NOTE: Remove restore_from from settings after successful restore.
|
|
242
|
-
const updated_settings = { ...settings };
|
|
243
|
-
delete updated_settings.restore_from;
|
|
244
|
-
|
|
245
|
-
// NOTE: Update the environment variable with the new settings.
|
|
246
|
-
process.env.JOYSTICK_DB_SETTINGS = JSON.stringify(updated_settings);
|
|
247
|
-
|
|
248
|
-
// NOTE: Reload settings to reflect the change.
|
|
249
|
-
load_settings();
|
|
250
|
-
settings = get_settings();
|
|
251
|
-
|
|
252
|
-
log.info('Removed restore_from from settings after successful restore');
|
|
253
|
-
} catch (restore_error) {
|
|
254
|
-
log.error('Startup restore failed', {
|
|
255
|
-
backup_filename: settings.restore_from,
|
|
256
|
-
error: restore_error.message
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
// NOTE: Continue with fresh database if restore fails.
|
|
260
|
-
log.info('Continuing with fresh database after restore failure');
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// NOTE: Initialize database with data_path from settings if available.
|
|
265
|
-
let database_path = './data'; // Default path
|
|
266
|
-
if (settings?.data_path) {
|
|
267
|
-
database_path = settings.data_path;
|
|
309
|
+
return null;
|
|
268
310
|
}
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
/**
|
|
314
|
+
* Initializes core server components.
|
|
315
|
+
* @param {Object} settings - Server settings
|
|
316
|
+
*/
|
|
317
|
+
const initialize_server_components = async (settings) => {
|
|
318
|
+
const database_path = settings?.data_path || './data';
|
|
269
319
|
|
|
270
320
|
initialize_database(database_path);
|
|
271
321
|
initialize_auth_manager();
|
|
272
322
|
await initialize_api_key_manager();
|
|
273
323
|
initialize_recovery_manager();
|
|
274
|
-
|
|
275
|
-
|
|
324
|
+
};
|
|
325
|
+
|
|
326
|
+
/**
|
|
327
|
+
* Initializes replication manager with error handling.
|
|
328
|
+
* @param {Function} log - Logger function
|
|
329
|
+
*/
|
|
330
|
+
const initialize_replication_with_logging = (log) => {
|
|
276
331
|
try {
|
|
277
332
|
initialize_replication_manager();
|
|
278
333
|
log.info('Replication manager initialized');
|
|
279
334
|
} catch (replication_error) {
|
|
280
335
|
log.warn('Failed to initialize replication manager', { error: replication_error.message });
|
|
281
336
|
}
|
|
282
|
-
|
|
283
|
-
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Initializes write forwarder with error handling.
|
|
341
|
+
* @param {Function} log - Logger function
|
|
342
|
+
*/
|
|
343
|
+
const initialize_write_forwarder_with_logging = (log) => {
|
|
284
344
|
try {
|
|
285
345
|
initialize_write_forwarder();
|
|
286
346
|
log.info('Write forwarder initialized');
|
|
287
347
|
} catch (forwarder_error) {
|
|
288
348
|
log.warn('Failed to initialize write forwarder', { error: forwarder_error.message });
|
|
289
349
|
}
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
/**
|
|
353
|
+
* Starts backup scheduling if S3 is configured.
|
|
354
|
+
* @param {Object} settings - Server settings
|
|
355
|
+
* @param {Function} log - Logger function
|
|
356
|
+
*/
|
|
357
|
+
const start_backup_scheduling = (settings, log) => {
|
|
358
|
+
if (!settings?.s3) {
|
|
359
|
+
return;
|
|
299
360
|
}
|
|
300
|
-
|
|
301
|
-
connection_manager = create_connection_manager({
|
|
302
|
-
max_connections: 1000,
|
|
303
|
-
idle_timeout: 10 * 60 * 1000,
|
|
304
|
-
request_timeout: 5 * 1000
|
|
305
|
-
});
|
|
306
|
-
|
|
307
|
-
// NOTE: Start HTTP server for setup and recovery operations.
|
|
308
|
-
let http_server_instance = null;
|
|
361
|
+
|
|
309
362
|
try {
|
|
310
|
-
|
|
311
|
-
|
|
363
|
+
start_backup_schedule();
|
|
364
|
+
log.info('Backup scheduling started');
|
|
365
|
+
} catch (backup_schedule_error) {
|
|
366
|
+
log.warn('Failed to start backup scheduling', { error: backup_schedule_error.message });
|
|
367
|
+
}
|
|
368
|
+
};
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* Starts HTTP server with error handling.
|
|
372
|
+
* @param {number} http_port - HTTP port number
|
|
373
|
+
* @param {Function} log - Logger function
|
|
374
|
+
* @returns {Object|null} HTTP server instance or null
|
|
375
|
+
*/
|
|
376
|
+
const start_http_server_with_logging = async (http_port, log) => {
|
|
377
|
+
try {
|
|
378
|
+
const http_server_instance = await start_http_server(http_port);
|
|
312
379
|
if (http_server_instance) {
|
|
313
380
|
log.info('HTTP server started', { http_port });
|
|
314
381
|
}
|
|
382
|
+
return http_server_instance;
|
|
315
383
|
} catch (http_error) {
|
|
316
384
|
log.warn('Failed to start HTTP server', { error: http_error.message });
|
|
385
|
+
return null;
|
|
317
386
|
}
|
|
318
|
-
|
|
319
|
-
|
|
387
|
+
};
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Displays appropriate startup messages based on environment.
|
|
391
|
+
*/
|
|
392
|
+
const display_startup_messages = () => {
|
|
320
393
|
if (is_development_mode()) {
|
|
321
394
|
const { tcp_port, http_port } = get_port_configuration();
|
|
322
395
|
display_development_startup_message(tcp_port, http_port);
|
|
323
396
|
} else {
|
|
324
397
|
warn_undefined_node_env();
|
|
325
398
|
}
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Creates connection manager instance.
|
|
403
|
+
* @returns {Object} Connection manager instance
|
|
404
|
+
*/
|
|
405
|
+
const create_server_connection_manager = () => {
|
|
406
|
+
return create_connection_manager({
|
|
407
|
+
max_connections: 1000,
|
|
408
|
+
idle_timeout: 10 * 60 * 1000,
|
|
409
|
+
request_timeout: 5 * 1000
|
|
410
|
+
});
|
|
411
|
+
};
|
|
412
|
+
|
|
413
|
+
/**
|
|
414
|
+
* Handles socket data processing.
|
|
415
|
+
* @param {net.Socket} socket - Client socket
|
|
416
|
+
* @param {Buffer} raw_data - Raw socket data
|
|
417
|
+
* @param {Object} message_parser - Message parser instance
|
|
418
|
+
* @param {Function} log - Logger function
|
|
419
|
+
*/
|
|
420
|
+
const handle_socket_data = async (socket, raw_data, message_parser, log) => {
|
|
421
|
+
connection_manager.update_activity(socket.id);
|
|
326
422
|
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
423
|
+
try {
|
|
424
|
+
const messages = message_parser.parse_messages(raw_data);
|
|
331
425
|
|
|
332
|
-
const
|
|
426
|
+
for (const message of messages) {
|
|
427
|
+
await process_single_message(socket, message, raw_data.length, log);
|
|
428
|
+
}
|
|
429
|
+
} catch (error) {
|
|
430
|
+
log.error('Message parsing failed', {
|
|
431
|
+
client_id: socket.id,
|
|
432
|
+
error: error.message
|
|
433
|
+
});
|
|
333
434
|
|
|
334
|
-
socket
|
|
335
|
-
|
|
435
|
+
send_error(socket, { message: 'Invalid message format' });
|
|
436
|
+
socket.end();
|
|
437
|
+
}
|
|
438
|
+
};
|
|
439
|
+
|
|
440
|
+
/**
|
|
441
|
+
* Processes a single parsed message.
|
|
442
|
+
* @param {net.Socket} socket - Client socket
|
|
443
|
+
* @param {Object} message - Parsed message
|
|
444
|
+
* @param {number} raw_data_size - Size of raw data
|
|
445
|
+
* @param {Function} log - Logger function
|
|
446
|
+
*/
|
|
447
|
+
const process_single_message = async (socket, message, raw_data_size, log) => {
|
|
448
|
+
const parsed_data = message;
|
|
449
|
+
const op_type = parsed_data?.op || null;
|
|
450
|
+
|
|
451
|
+
if (!op_type) {
|
|
452
|
+
send_error(socket, { message: 'Missing operation type' });
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const is_valid_op_type = check_op_type(op_type);
|
|
457
|
+
|
|
458
|
+
if (!is_valid_op_type) {
|
|
459
|
+
send_error(socket, { message: 'Invalid operation type' });
|
|
460
|
+
return;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const request_timeout = connection_manager.create_request_timeout(socket.id, op_type);
|
|
464
|
+
|
|
465
|
+
try {
|
|
466
|
+
await route_operation_to_handler(socket, op_type, parsed_data, raw_data_size);
|
|
467
|
+
} finally {
|
|
468
|
+
clearTimeout(request_timeout);
|
|
469
|
+
}
|
|
470
|
+
};
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* Routes operation to appropriate handler.
|
|
474
|
+
* @param {net.Socket} socket - Client socket
|
|
475
|
+
* @param {string} op_type - Operation type
|
|
476
|
+
* @param {Object} parsed_data - Parsed message data
|
|
477
|
+
* @param {number} raw_data_size - Size of raw data
|
|
478
|
+
*/
|
|
479
|
+
const route_operation_to_handler = async (socket, op_type, parsed_data, raw_data_size) => {
|
|
480
|
+
const operation_data = parsed_data?.data || {};
|
|
481
|
+
|
|
482
|
+
switch(op_type) {
|
|
483
|
+
case 'authentication':
|
|
484
|
+
await authentication(socket, operation_data);
|
|
485
|
+
break;
|
|
336
486
|
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
case 'update_one':
|
|
371
|
-
case 'delete_one':
|
|
372
|
-
case 'delete_many':
|
|
373
|
-
case 'bulk_write':
|
|
374
|
-
case 'find_one':
|
|
375
|
-
case 'find':
|
|
376
|
-
case 'create_index':
|
|
377
|
-
case 'drop_index':
|
|
378
|
-
case 'get_indexes':
|
|
379
|
-
// NOTE: Use shared operation dispatcher for database operations.
|
|
380
|
-
await handle_database_operation(socket, op_type, parsed_data?.data || {}, check_authentication, raw_data.length, connection_manager, authenticated_clients);
|
|
381
|
-
break;
|
|
382
|
-
|
|
383
|
-
case 'ping':
|
|
384
|
-
handle_ping_operation(socket);
|
|
385
|
-
break;
|
|
386
|
-
|
|
387
|
-
case 'admin':
|
|
388
|
-
// NOTE: Use shared operation dispatcher for admin operations.
|
|
389
|
-
await handle_admin_operation(socket, parsed_data?.data || {}, check_authentication, connection_manager, authenticated_clients);
|
|
390
|
-
break;
|
|
391
|
-
|
|
392
|
-
case 'reload':
|
|
393
|
-
if (!check_authentication(socket)) {
|
|
394
|
-
send_error(socket, { message: 'Authentication required' });
|
|
395
|
-
break;
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
try {
|
|
399
|
-
let old_settings = null;
|
|
400
|
-
try {
|
|
401
|
-
old_settings = get_settings();
|
|
402
|
-
} catch (error) {
|
|
403
|
-
// NOTE: Settings not loaded yet, that's OK.
|
|
404
|
-
}
|
|
405
|
-
|
|
406
|
-
let new_settings = null;
|
|
407
|
-
try {
|
|
408
|
-
await load_settings();
|
|
409
|
-
new_settings = get_settings();
|
|
410
|
-
} catch (error) {
|
|
411
|
-
// NOTE: Settings file doesn't exist, create default settings for tests.
|
|
412
|
-
new_settings = {
|
|
413
|
-
port: 1983,
|
|
414
|
-
authentication: {}
|
|
415
|
-
};
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
const reload_info = {
|
|
419
|
-
ok: 1,
|
|
420
|
-
status: 'success',
|
|
421
|
-
message: 'Configuration reloaded successfully',
|
|
422
|
-
changes: {
|
|
423
|
-
port_changed: old_settings ? old_settings.port !== new_settings.port : false,
|
|
424
|
-
authentication_changed: old_settings ? old_settings.authentication?.password_hash !== new_settings.authentication?.password_hash : false
|
|
425
|
-
},
|
|
426
|
-
timestamp: new Date().toISOString()
|
|
427
|
-
};
|
|
428
|
-
|
|
429
|
-
const encoded_response = encode_message(reload_info);
|
|
430
|
-
socket.write(encoded_response);
|
|
431
|
-
} catch (error) {
|
|
432
|
-
const error_response = { ok: 0, error: `Reload operation failed: ${error.message}` };
|
|
433
|
-
const encoded_error_response = encode_message(error_response);
|
|
434
|
-
socket.write(encoded_error_response);
|
|
435
|
-
}
|
|
436
|
-
break;
|
|
437
|
-
|
|
438
|
-
default:
|
|
439
|
-
send_error(socket, { message: `Operation ${op_type} not implemented` });
|
|
440
|
-
}
|
|
441
|
-
} finally {
|
|
442
|
-
clearTimeout(request_timeout);
|
|
443
|
-
}
|
|
444
|
-
}
|
|
445
|
-
} catch (error) {
|
|
446
|
-
log.error('Message parsing failed', {
|
|
447
|
-
client_id: socket.id,
|
|
448
|
-
error: error.message
|
|
449
|
-
});
|
|
450
|
-
|
|
451
|
-
send_error(socket, { message: 'Invalid message format' });
|
|
452
|
-
socket.end();
|
|
453
|
-
}
|
|
454
|
-
});
|
|
487
|
+
case 'setup':
|
|
488
|
+
await setup(socket, operation_data);
|
|
489
|
+
break;
|
|
490
|
+
|
|
491
|
+
case 'insert_one':
|
|
492
|
+
case 'update_one':
|
|
493
|
+
case 'delete_one':
|
|
494
|
+
case 'delete_many':
|
|
495
|
+
case 'bulk_write':
|
|
496
|
+
case 'find_one':
|
|
497
|
+
case 'find':
|
|
498
|
+
case 'create_index':
|
|
499
|
+
case 'drop_index':
|
|
500
|
+
case 'get_indexes':
|
|
501
|
+
await handle_database_operation(socket, op_type, operation_data, check_authentication, raw_data_size, connection_manager, authenticated_clients);
|
|
502
|
+
break;
|
|
503
|
+
|
|
504
|
+
case 'ping':
|
|
505
|
+
handle_ping_operation(socket);
|
|
506
|
+
break;
|
|
507
|
+
|
|
508
|
+
case 'admin':
|
|
509
|
+
await handle_admin_operation(socket, operation_data, check_authentication, connection_manager, authenticated_clients);
|
|
510
|
+
break;
|
|
511
|
+
|
|
512
|
+
case 'reload':
|
|
513
|
+
await handle_reload_operation(socket);
|
|
514
|
+
break;
|
|
515
|
+
|
|
516
|
+
default:
|
|
517
|
+
send_error(socket, { message: `Operation ${op_type} not implemented` });
|
|
518
|
+
}
|
|
519
|
+
};
|
|
455
520
|
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
521
|
+
/**
|
|
522
|
+
* Handles reload operation.
|
|
523
|
+
* @param {net.Socket} socket - Client socket
|
|
524
|
+
*/
|
|
525
|
+
const handle_reload_operation = async (socket) => {
|
|
526
|
+
if (!check_authentication(socket)) {
|
|
527
|
+
send_error(socket, { message: 'Authentication required' });
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
try {
|
|
532
|
+
const old_settings = get_current_settings();
|
|
533
|
+
const new_settings = await reload_server_settings();
|
|
461
534
|
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
535
|
+
const reload_info = create_reload_response(old_settings, new_settings);
|
|
536
|
+
send_response_to_socket(socket, reload_info);
|
|
537
|
+
} catch (error) {
|
|
538
|
+
const error_response = { ok: 0, error: `Reload operation failed: ${error.message}` };
|
|
539
|
+
send_response_to_socket(socket, error_response);
|
|
540
|
+
}
|
|
541
|
+
};
|
|
542
|
+
|
|
543
|
+
/**
|
|
544
|
+
* Gets current settings with fallback.
|
|
545
|
+
* @returns {Object|null} Current settings or null
|
|
546
|
+
*/
|
|
547
|
+
const get_current_settings = () => {
|
|
548
|
+
try {
|
|
549
|
+
return get_settings();
|
|
550
|
+
} catch (error) {
|
|
551
|
+
return null;
|
|
552
|
+
}
|
|
553
|
+
};
|
|
554
|
+
|
|
555
|
+
/**
|
|
556
|
+
* Reloads server settings.
|
|
557
|
+
* @returns {Object} New settings object
|
|
558
|
+
*/
|
|
559
|
+
const reload_server_settings = async () => {
|
|
560
|
+
try {
|
|
561
|
+
await load_settings();
|
|
562
|
+
return get_settings();
|
|
563
|
+
} catch (error) {
|
|
564
|
+
return {
|
|
565
|
+
port: 1983,
|
|
566
|
+
authentication: {}
|
|
567
|
+
};
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
|
|
571
|
+
/**
|
|
572
|
+
* Creates reload operation response.
|
|
573
|
+
* @param {Object} old_settings - Previous settings
|
|
574
|
+
* @param {Object} new_settings - New settings
|
|
575
|
+
* @returns {Object} Reload response object
|
|
576
|
+
*/
|
|
577
|
+
const create_reload_response = (old_settings, new_settings) => ({
|
|
578
|
+
ok: 1,
|
|
579
|
+
status: 'success',
|
|
580
|
+
message: 'Configuration reloaded successfully',
|
|
581
|
+
changes: {
|
|
582
|
+
port_changed: old_settings ? old_settings.port !== new_settings.port : false,
|
|
583
|
+
authentication_changed: old_settings ? old_settings.authentication?.password_hash !== new_settings.authentication?.password_hash : false
|
|
584
|
+
},
|
|
585
|
+
timestamp: new Date().toISOString()
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Handles socket disconnection.
|
|
590
|
+
* @param {net.Socket} socket - Client socket
|
|
591
|
+
* @param {Function} log - Logger function
|
|
592
|
+
*/
|
|
593
|
+
const handle_socket_disconnection = (socket, log) => {
|
|
594
|
+
log.info('Client disconnected', { socket_id: socket.id });
|
|
595
|
+
authenticated_clients.delete(socket.id);
|
|
596
|
+
connection_manager.remove_connection(socket.id);
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Handles socket errors.
|
|
601
|
+
* @param {net.Socket} socket - Client socket
|
|
602
|
+
* @param {Error} error - Socket error
|
|
603
|
+
* @param {Function} log - Logger function
|
|
604
|
+
*/
|
|
605
|
+
const handle_socket_error = (socket, error, log) => {
|
|
606
|
+
log.error('Socket error', {
|
|
607
|
+
socket_id: socket.id,
|
|
608
|
+
error: error.message
|
|
609
|
+
});
|
|
610
|
+
|
|
611
|
+
authenticated_clients.delete(socket.id);
|
|
612
|
+
connection_manager.remove_connection(socket.id);
|
|
613
|
+
};
|
|
614
|
+
|
|
615
|
+
/**
|
|
616
|
+
* Sets up socket event handlers.
|
|
617
|
+
* @param {net.Socket} socket - Client socket
|
|
618
|
+
* @param {Object} message_parser - Message parser instance
|
|
619
|
+
* @param {Function} log - Logger function
|
|
620
|
+
*/
|
|
621
|
+
const setup_socket_event_handlers = (socket, message_parser, log) => {
|
|
622
|
+
socket.on('data', async (raw_data) => {
|
|
623
|
+
await handle_socket_data(socket, raw_data, message_parser, log);
|
|
471
624
|
});
|
|
625
|
+
|
|
626
|
+
socket.on('end', () => {
|
|
627
|
+
handle_socket_disconnection(socket, log);
|
|
628
|
+
});
|
|
629
|
+
|
|
630
|
+
socket.on('error', (error) => {
|
|
631
|
+
handle_socket_error(socket, error, log);
|
|
632
|
+
});
|
|
633
|
+
};
|
|
634
|
+
|
|
635
|
+
/**
|
|
636
|
+
* Handles new socket connections.
|
|
637
|
+
* @param {net.Socket} socket - New client socket
|
|
638
|
+
* @param {Function} log - Logger function
|
|
639
|
+
*/
|
|
640
|
+
const handle_new_socket_connection = (socket, log) => {
|
|
641
|
+
if (!connection_manager.add_connection(socket)) {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
472
644
|
|
|
473
|
-
|
|
474
|
-
|
|
645
|
+
const message_parser = create_message_parser();
|
|
646
|
+
setup_socket_event_handlers(socket, message_parser, log);
|
|
647
|
+
};
|
|
648
|
+
|
|
649
|
+
/**
|
|
650
|
+
* Creates cleanup function for server shutdown.
|
|
651
|
+
* @returns {Function} Cleanup function
|
|
652
|
+
*/
|
|
653
|
+
const create_server_cleanup_function = () => {
|
|
654
|
+
return async () => {
|
|
475
655
|
try {
|
|
476
|
-
// NOTE: Stop HTTP server.
|
|
477
656
|
await stop_http_server();
|
|
478
|
-
|
|
479
|
-
// NOTE: Stop backup scheduling.
|
|
480
657
|
stop_backup_schedule();
|
|
481
|
-
|
|
482
|
-
// NOTE: Shutdown replication manager.
|
|
483
658
|
await shutdown_replication_manager();
|
|
484
|
-
|
|
485
|
-
// NOTE: Shutdown write forwarder.
|
|
486
659
|
await shutdown_write_forwarder();
|
|
487
660
|
|
|
488
|
-
// NOTE: Shutdown connection manager.
|
|
489
661
|
if (connection_manager) {
|
|
490
662
|
connection_manager.shutdown();
|
|
491
663
|
}
|
|
492
664
|
|
|
493
|
-
// NOTE: Clear authenticated clients.
|
|
494
665
|
authenticated_clients.clear();
|
|
495
|
-
|
|
496
|
-
// NOTE: Shutdown write queue.
|
|
497
666
|
await shutdown_write_queue();
|
|
498
667
|
|
|
499
|
-
// NOTE: Wait a bit to ensure all operations are complete.
|
|
500
668
|
await new Promise(resolve => setTimeout(resolve, 100));
|
|
501
|
-
|
|
502
|
-
// NOTE: Cleanup database (after everything else is shut down).
|
|
503
669
|
await cleanup_database();
|
|
504
670
|
|
|
505
|
-
// NOTE: Reset auth state.
|
|
506
671
|
reset_auth_state();
|
|
507
|
-
|
|
508
|
-
// NOTE: Reset recovery state.
|
|
509
672
|
reset_recovery_state();
|
|
510
673
|
} catch (error) {
|
|
511
674
|
// NOTE: Ignore cleanup errors in tests.
|
|
512
675
|
}
|
|
513
676
|
};
|
|
514
|
-
|
|
515
|
-
return server;
|
|
516
677
|
};
|
|
517
678
|
|
|
518
|
-
|
|
519
|
-
|
|
679
|
+
/**
|
|
680
|
+
* @typedef {Object} ServerOptions
|
|
681
|
+
* @property {number} [worker_count] - Number of worker processes
|
|
682
|
+
* @property {number} [port=1983] - Server port
|
|
683
|
+
* @property {string} [environment] - Environment name for settings file (defaults to NODE_ENV)
|
|
684
|
+
*/
|
|
685
|
+
|
|
686
|
+
/**
|
|
687
|
+
* Creates and configures the JoystickDB TCP server.
|
|
688
|
+
* @returns {Promise<net.Server>} Configured TCP server instance with cleanup method
|
|
689
|
+
*/
|
|
690
|
+
export const create_server = async () => {
|
|
691
|
+
const { create_context_logger } = create_logger('server');
|
|
520
692
|
const log = create_context_logger();
|
|
521
693
|
|
|
522
|
-
|
|
523
|
-
if (process.argv.includes('--generate-recovery-token')) {
|
|
524
|
-
try {
|
|
525
|
-
initialize_recovery_manager();
|
|
526
|
-
const recovery_info = create_recovery_token();
|
|
527
|
-
|
|
528
|
-
console.log('Emergency Recovery Token Generated');
|
|
529
|
-
console.log(`Visit: ${recovery_info.url}`);
|
|
530
|
-
console.log('Token expires in 10 minutes');
|
|
531
|
-
|
|
532
|
-
log.info('Recovery token generated via CLI', {
|
|
533
|
-
expires_at: new Date(recovery_info.expires_at).toISOString()
|
|
534
|
-
});
|
|
535
|
-
|
|
536
|
-
process.exit(0);
|
|
537
|
-
} catch (error) {
|
|
538
|
-
console.error('Failed to generate recovery token:', error.message);
|
|
539
|
-
log.error('Recovery token generation failed', { error: error.message });
|
|
540
|
-
process.exit(1);
|
|
541
|
-
}
|
|
542
|
-
}
|
|
694
|
+
const settings = load_server_settings();
|
|
543
695
|
|
|
544
|
-
|
|
545
|
-
|
|
696
|
+
await attempt_startup_restore(settings, log);
|
|
697
|
+
await initialize_server_components(settings);
|
|
698
|
+
|
|
699
|
+
initialize_replication_with_logging(log);
|
|
700
|
+
initialize_write_forwarder_with_logging(log);
|
|
701
|
+
start_backup_scheduling(settings, log);
|
|
702
|
+
|
|
703
|
+
connection_manager = create_server_connection_manager();
|
|
546
704
|
|
|
547
|
-
const
|
|
705
|
+
const { http_port } = get_port_configuration();
|
|
706
|
+
await start_http_server_with_logging(http_port, log);
|
|
707
|
+
|
|
708
|
+
display_startup_messages();
|
|
709
|
+
|
|
710
|
+
const server = net.createServer((socket = {}) => {
|
|
711
|
+
handle_new_socket_connection(socket, log);
|
|
712
|
+
});
|
|
713
|
+
|
|
714
|
+
server.cleanup = create_server_cleanup_function();
|
|
715
|
+
|
|
716
|
+
return server;
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* Handles recovery token generation from command line.
|
|
721
|
+
* @param {Function} log - Logger function
|
|
722
|
+
*/
|
|
723
|
+
const handle_recovery_token_generation = (log) => {
|
|
724
|
+
try {
|
|
725
|
+
initialize_recovery_manager();
|
|
726
|
+
const recovery_info = create_recovery_token();
|
|
727
|
+
|
|
728
|
+
console.log('Emergency Recovery Token Generated');
|
|
729
|
+
console.log(`Visit: ${recovery_info.url}`);
|
|
730
|
+
console.log('Token expires in 10 minutes');
|
|
731
|
+
|
|
732
|
+
log.info('Recovery token generated via CLI', {
|
|
733
|
+
expires_at: new Date(recovery_info.expires_at).toISOString()
|
|
734
|
+
});
|
|
735
|
+
|
|
736
|
+
process.exit(0);
|
|
737
|
+
} catch (error) {
|
|
738
|
+
console.error('Failed to generate recovery token:', error.message);
|
|
739
|
+
log.error('Recovery token generation failed', { error: error.message });
|
|
740
|
+
process.exit(1);
|
|
741
|
+
}
|
|
742
|
+
};
|
|
743
|
+
|
|
744
|
+
/**
|
|
745
|
+
* Creates server startup options.
|
|
746
|
+
* @returns {Object} Server options object
|
|
747
|
+
*/
|
|
748
|
+
const create_server_startup_options = () => {
|
|
749
|
+
const { tcp_port } = get_port_configuration();
|
|
750
|
+
|
|
751
|
+
return {
|
|
548
752
|
worker_count: process.env.WORKER_COUNT ? parseInt(process.env.WORKER_COUNT) : undefined,
|
|
549
753
|
port: tcp_port,
|
|
550
754
|
environment: process.env.NODE_ENV || 'development'
|
|
551
755
|
};
|
|
552
|
-
|
|
553
|
-
|
|
756
|
+
};
|
|
757
|
+
|
|
758
|
+
/**
|
|
759
|
+
* Logs server startup information.
|
|
760
|
+
* @param {Object} options - Server options
|
|
761
|
+
* @param {Function} log - Logger function
|
|
762
|
+
*/
|
|
763
|
+
const log_server_startup_info = (options, log) => {
|
|
764
|
+
const { tcp_port, http_port } = get_port_configuration();
|
|
554
765
|
const has_db_settings = has_settings();
|
|
555
766
|
|
|
556
767
|
log.info('Starting JoystickDB server...', {
|
|
@@ -561,6 +772,18 @@ if (import.meta.url === `file://${process.argv[1]}`) {
|
|
|
561
772
|
has_settings: has_db_settings,
|
|
562
773
|
port_source: has_db_settings ? 'JOYSTICK_DB_SETTINGS' : 'default'
|
|
563
774
|
});
|
|
775
|
+
};
|
|
776
|
+
|
|
777
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
778
|
+
const { create_context_logger } = create_logger('main');
|
|
779
|
+
const log = create_context_logger();
|
|
780
|
+
|
|
781
|
+
if (process.argv.includes('--generate-recovery-token')) {
|
|
782
|
+
handle_recovery_token_generation(log);
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
const options = create_server_startup_options();
|
|
786
|
+
log_server_startup_info(options, log);
|
|
564
787
|
|
|
565
788
|
start_cluster(options);
|
|
566
789
|
}
|