@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.
Files changed (39) hide show
  1. package/README.md +87 -104
  2. package/debug_test_runner.js +208 -0
  3. package/dist/client/index.js +1 -1
  4. package/dist/server/cluster/master.js +2 -2
  5. package/dist/server/cluster/worker.js +1 -1
  6. package/dist/server/index.js +1 -1
  7. package/dist/server/lib/auto_index_manager.js +1 -1
  8. package/dist/server/lib/bulk_insert_optimizer.js +1 -1
  9. package/dist/server/lib/http_server.js +3 -3
  10. package/dist/server/lib/operation_dispatcher.js +1 -1
  11. package/dist/server/lib/operations/admin.js +1 -1
  12. package/dist/server/lib/operations/update_one.js +1 -1
  13. package/dist/server/lib/simple_sync_manager.js +1 -0
  14. package/dist/server/lib/sync_receiver.js +1 -0
  15. package/full_debug_test_runner.js +197 -0
  16. package/package.json +10 -7
  17. package/src/client/index.js +1 -0
  18. package/src/server/cluster/master.js +8 -2
  19. package/src/server/cluster/worker.js +9 -3
  20. package/src/server/index.js +25 -24
  21. package/src/server/lib/auto_index_manager.js +8 -3
  22. package/src/server/lib/bulk_insert_optimizer.js +79 -0
  23. package/src/server/lib/http_server.js +7 -0
  24. package/src/server/lib/operation_dispatcher.js +16 -10
  25. package/src/server/lib/operations/admin.js +64 -31
  26. package/src/server/lib/operations/update_one.js +251 -1
  27. package/src/server/lib/simple_sync_manager.js +444 -0
  28. package/src/server/lib/sync_receiver.js +461 -0
  29. package/tests/client/index.test.js +7 -0
  30. package/tests/performance/isolated_5000000_test.js +184 -0
  31. package/tests/server/lib/http_server.test.js +3 -12
  32. package/tests/server/lib/operations/update_one.test.js +161 -0
  33. package/tests/server/lib/simple_sync_system.test.js +124 -0
  34. package/dist/server/lib/replication_manager.js +0 -1
  35. package/dist/server/lib/write_forwarder.js +0 -1
  36. package/src/server/lib/replication_manager.js +0 -727
  37. package/src/server/lib/write_forwarder.js +0 -636
  38. package/tests/server/lib/replication_manager.test.js +0 -202
  39. package/tests/server/lib/write_forwarder.test.js +0 -258
@@ -175,10 +175,228 @@ const apply_pull_operator = (document, operations) => {
175
175
  return updated_document;
176
176
  };
177
177
 
178
+ /**
179
+ * Applies $add_to_set operator to document.
180
+ * @param {Object} document - Document to update
181
+ * @param {Object} operations - AddToSet operations
182
+ * @returns {Object} Updated document
183
+ */
184
+ const apply_add_to_set_operator = (document, operations) => {
185
+ let updated_document = { ...document };
186
+
187
+ for (const [field, value] of Object.entries(operations)) {
188
+ if (field.includes('.')) {
189
+ // Handle nested field add_to_set with dot notation
190
+ let current_array = get_nested_field(updated_document, field);
191
+ if (!Array.isArray(current_array)) {
192
+ current_array = [];
193
+ }
194
+
195
+ // Only add if the value doesn't already exist in the array
196
+ if (!current_array.includes(value)) {
197
+ current_array = [...current_array, value];
198
+ }
199
+
200
+ updated_document = set_nested_field(updated_document, field, current_array);
201
+ } else {
202
+ // Handle simple field add_to_set
203
+ if (!Array.isArray(updated_document[field])) {
204
+ updated_document[field] = [];
205
+ }
206
+
207
+ // Only add if the value doesn't already exist in the array
208
+ if (!updated_document[field].includes(value)) {
209
+ updated_document[field] = [...updated_document[field], value];
210
+ }
211
+ }
212
+ }
213
+
214
+ return updated_document;
215
+ };
216
+
217
+ /**
218
+ * Applies $pull_all operator to document.
219
+ * @param {Object} document - Document to update
220
+ * @param {Object} operations - PullAll operations
221
+ * @returns {Object} Updated document
222
+ */
223
+ const apply_pull_all_operator = (document, operations) => {
224
+ const updated_document = { ...document };
225
+ for (const [field, values] of Object.entries(operations)) {
226
+ if (Array.isArray(updated_document[field]) && Array.isArray(values)) {
227
+ updated_document[field] = updated_document[field].filter(item => !values.includes(item));
228
+ }
229
+ }
230
+ return updated_document;
231
+ };
232
+
233
+ /**
234
+ * Applies $pop operator to document.
235
+ * @param {Object} document - Document to update
236
+ * @param {Object} operations - Pop operations
237
+ * @returns {Object} Updated document
238
+ */
239
+ const apply_pop_operator = (document, operations) => {
240
+ const updated_document = { ...document };
241
+ for (const [field, direction] of Object.entries(operations)) {
242
+ if (Array.isArray(updated_document[field]) && updated_document[field].length > 0) {
243
+ if (direction === 1 || direction === '1') {
244
+ // Remove last element
245
+ updated_document[field] = updated_document[field].slice(0, -1);
246
+ } else if (direction === -1 || direction === '-1') {
247
+ // Remove first element
248
+ updated_document[field] = updated_document[field].slice(1);
249
+ }
250
+ }
251
+ }
252
+ return updated_document;
253
+ };
254
+
255
+ /**
256
+ * Applies $rename operator to document.
257
+ * @param {Object} document - Document to update
258
+ * @param {Object} operations - Rename operations
259
+ * @returns {Object} Updated document
260
+ */
261
+ const apply_rename_operator = (document, operations) => {
262
+ let updated_document = { ...document };
263
+
264
+ for (const [old_field, new_field] of Object.entries(operations)) {
265
+ if (old_field.includes('.') || new_field.includes('.')) {
266
+ // Handle nested field renames with dot notation
267
+ const old_value = get_nested_field(updated_document, old_field);
268
+ if (old_value !== undefined) {
269
+ updated_document = set_nested_field(updated_document, new_field, old_value);
270
+ updated_document = unset_nested_field(updated_document, old_field);
271
+ }
272
+ } else {
273
+ // Handle simple field renames
274
+ if (old_field in updated_document) {
275
+ updated_document[new_field] = updated_document[old_field];
276
+ delete updated_document[old_field];
277
+ }
278
+ }
279
+ }
280
+
281
+ return updated_document;
282
+ };
283
+
284
+ /**
285
+ * Applies $min operator to document.
286
+ * @param {Object} document - Document to update
287
+ * @param {Object} operations - Min operations
288
+ * @returns {Object} Updated document
289
+ */
290
+ const apply_min_operator = (document, operations) => {
291
+ let updated_document = { ...document };
292
+
293
+ for (const [field, value] of Object.entries(operations)) {
294
+ if (field.includes('.')) {
295
+ // Handle nested field min with dot notation
296
+ const current_value = get_nested_field(updated_document, field);
297
+ if (current_value === undefined || value < current_value) {
298
+ updated_document = set_nested_field(updated_document, field, value);
299
+ }
300
+ } else {
301
+ // Handle simple field min
302
+ if (!(field in updated_document) || value < updated_document[field]) {
303
+ updated_document[field] = value;
304
+ }
305
+ }
306
+ }
307
+
308
+ return updated_document;
309
+ };
310
+
311
+ /**
312
+ * Applies $max operator to document.
313
+ * @param {Object} document - Document to update
314
+ * @param {Object} operations - Max operations
315
+ * @returns {Object} Updated document
316
+ */
317
+ const apply_max_operator = (document, operations) => {
318
+ let updated_document = { ...document };
319
+
320
+ for (const [field, value] of Object.entries(operations)) {
321
+ if (field.includes('.')) {
322
+ // Handle nested field max with dot notation
323
+ const current_value = get_nested_field(updated_document, field);
324
+ if (current_value === undefined || value > current_value) {
325
+ updated_document = set_nested_field(updated_document, field, value);
326
+ }
327
+ } else {
328
+ // Handle simple field max
329
+ if (!(field in updated_document) || value > updated_document[field]) {
330
+ updated_document[field] = value;
331
+ }
332
+ }
333
+ }
334
+
335
+ return updated_document;
336
+ };
337
+
338
+ /**
339
+ * Applies $mul operator to document.
340
+ * @param {Object} document - Document to update
341
+ * @param {Object} operations - Multiply operations
342
+ * @returns {Object} Updated document
343
+ */
344
+ const apply_mul_operator = (document, operations) => {
345
+ let updated_document = { ...document };
346
+
347
+ for (const [field, value] of Object.entries(operations)) {
348
+ if (field.includes('.')) {
349
+ // Handle nested field multiply with dot notation
350
+ const current_value = get_nested_field(updated_document, field) || 0;
351
+ updated_document = set_nested_field(updated_document, field, current_value * value);
352
+ } else {
353
+ // Handle simple field multiply
354
+ updated_document[field] = (updated_document[field] || 0) * value;
355
+ }
356
+ }
357
+
358
+ return updated_document;
359
+ };
360
+
361
+ /**
362
+ * Applies $current_date operator to document.
363
+ * @param {Object} document - Document to update
364
+ * @param {Object} operations - CurrentDate operations
365
+ * @returns {Object} Updated document
366
+ */
367
+ const apply_current_date_operator = (document, operations) => {
368
+ let updated_document = { ...document };
369
+ const current_date = new Date();
370
+
371
+ for (const [field, type_spec] of Object.entries(operations)) {
372
+ let date_value;
373
+
374
+ if (type_spec === true) {
375
+ date_value = current_date;
376
+ } else if (typeof type_spec === 'object' && type_spec !== null && type_spec.$type === 'date') {
377
+ date_value = current_date;
378
+ } else if (typeof type_spec === 'object' && type_spec !== null && type_spec.$type === 'timestamp') {
379
+ date_value = current_date.toISOString();
380
+ } else {
381
+ date_value = current_date;
382
+ }
383
+
384
+ if (field.includes('.')) {
385
+ // Handle nested field current date with dot notation
386
+ updated_document = set_nested_field(updated_document, field, date_value);
387
+ } else {
388
+ // Handle simple field current date
389
+ updated_document[field] = date_value;
390
+ }
391
+ }
392
+
393
+ return updated_document;
394
+ };
395
+
178
396
  /**
179
397
  * Applies MongoDB-style update operators to a document.
180
398
  * @param {Object} document - Original document
181
- * @param {Object} update_operations - Update operations ($set, $unset, $inc, $push, $pull)
399
+ * @param {Object} update_operations - Update operations with supported operators
182
400
  * @returns {Object} Updated document
183
401
  * @throws {Error} When unsupported update operator is used
184
402
  */
@@ -207,6 +425,38 @@ const apply_update_operators = (document, update_operations) => {
207
425
  updated_document = apply_pull_operator(updated_document, operations);
208
426
  break;
209
427
 
428
+ case '$add_to_set':
429
+ updated_document = apply_add_to_set_operator(updated_document, operations);
430
+ break;
431
+
432
+ case '$pull_all':
433
+ updated_document = apply_pull_all_operator(updated_document, operations);
434
+ break;
435
+
436
+ case '$pop':
437
+ updated_document = apply_pop_operator(updated_document, operations);
438
+ break;
439
+
440
+ case '$rename':
441
+ updated_document = apply_rename_operator(updated_document, operations);
442
+ break;
443
+
444
+ case '$min':
445
+ updated_document = apply_min_operator(updated_document, operations);
446
+ break;
447
+
448
+ case '$max':
449
+ updated_document = apply_max_operator(updated_document, operations);
450
+ break;
451
+
452
+ case '$mul':
453
+ updated_document = apply_mul_operator(updated_document, operations);
454
+ break;
455
+
456
+ case '$current_date':
457
+ updated_document = apply_current_date_operator(updated_document, operations);
458
+ break;
459
+
210
460
  default:
211
461
  throw new Error(`Unsupported update operator: ${operator}`);
212
462
  }
@@ -0,0 +1,444 @@
1
+ /**
2
+ * @fileoverview Simple sync manager for primary nodes in JoystickDB.
3
+ * Provides basic TCP synchronization of write operations to secondary nodes
4
+ * with API_KEY authentication and simple retry logic.
5
+ */
6
+
7
+ import net from 'net';
8
+ import { get_settings } from './load_settings.js';
9
+ import { encode_message } from './tcp_protocol.js';
10
+ import create_logger from './logger.js';
11
+
12
+ const { create_context_logger } = create_logger('simple_sync');
13
+
14
+ /**
15
+ * Simple sync manager that syncs write operations to secondary nodes.
16
+ * Uses API_KEY authentication and basic TCP connections with retry logic.
17
+ */
18
+ class SimpleSyncManager {
19
+ /**
20
+ * Creates a new SimpleSyncManager instance.
21
+ */
22
+ constructor() {
23
+ /** @type {boolean} Whether this node is configured as primary */
24
+ this.is_primary = false;
25
+
26
+ /** @type {Array<Object>} List of secondary nodes to sync to */
27
+ this.secondary_nodes = [];
28
+
29
+ /** @type {Map<string, Object>} Active connections to secondary nodes */
30
+ this.connections = new Map();
31
+
32
+ /** @type {number} TCP port for sync operations */
33
+ this.sync_port = 1985;
34
+
35
+ /** @type {number} Timeout for sync operations in milliseconds */
36
+ this.sync_timeout_ms = 5000;
37
+
38
+ /** @type {number} Number of retry attempts */
39
+ this.sync_retries = 2;
40
+
41
+ /** @type {number} Sequence number for operations */
42
+ this.sequence_number = 0;
43
+
44
+ /** @type {Object} Logger instance */
45
+ this.log = create_context_logger();
46
+
47
+ /** @type {Object} Sync statistics */
48
+ this.stats = {
49
+ total_synced: 0,
50
+ successful_syncs: 0,
51
+ failed_syncs: 0,
52
+ auth_failures: 0
53
+ };
54
+ }
55
+
56
+ /**
57
+ * Initializes the sync manager with settings configuration.
58
+ */
59
+ initialize() {
60
+ try {
61
+ const settings = get_settings();
62
+
63
+ if (!settings.primary) {
64
+ this.log.info('Node not configured as primary - sync disabled');
65
+ return;
66
+ }
67
+
68
+ this.is_primary = settings.primary;
69
+ this.secondary_nodes = settings.secondary_nodes || [];
70
+ this.sync_port = settings.sync_port || 1985;
71
+ this.sync_timeout_ms = settings.sync_timeout_ms || 5000;
72
+ this.sync_retries = settings.sync_retries || 2;
73
+
74
+ this.log.info('Initializing simple sync manager', {
75
+ is_primary: this.is_primary,
76
+ secondary_count: this.secondary_nodes.length,
77
+ sync_port: this.sync_port,
78
+ timeout_ms: this.sync_timeout_ms,
79
+ retries: this.sync_retries
80
+ });
81
+
82
+ this.connect_to_secondaries();
83
+
84
+ } catch (error) {
85
+ this.log.warn('Could not initialize sync manager - settings not loaded', {
86
+ error: error.message
87
+ });
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Establishes connections to all secondary nodes.
93
+ */
94
+ connect_to_secondaries() {
95
+ for (const secondary of this.secondary_nodes) {
96
+ this.connect_to_secondary(secondary);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Connects to a single secondary node.
102
+ * @param {Object} secondary - Secondary node configuration
103
+ */
104
+ connect_to_secondary(secondary) {
105
+ const { ip } = secondary;
106
+ const connection_id = `${ip}:${this.sync_port}`;
107
+
108
+ if (this.connections.has(connection_id)) {
109
+ return;
110
+ }
111
+
112
+ this.log.info('Connecting to secondary node', { ip, port: this.sync_port });
113
+
114
+ try {
115
+ const socket = new net.Socket();
116
+
117
+ socket.connect(this.sync_port, ip, () => {
118
+ this.log.info('Connected to secondary node', { ip, port: this.sync_port });
119
+ });
120
+
121
+ socket.on('error', (error) => {
122
+ this.log.error('Secondary connection error', {
123
+ ip,
124
+ error: error.message
125
+ });
126
+
127
+ this.connections.delete(connection_id);
128
+
129
+ // Retry connection after delay
130
+ setTimeout(() => {
131
+ try {
132
+ this.connect_to_secondary(secondary);
133
+ } catch (retry_error) {
134
+ this.log.error('Failed to retry secondary connection', {
135
+ ip,
136
+ error: retry_error.message
137
+ });
138
+ }
139
+ }, 5000);
140
+ });
141
+
142
+ socket.on('close', () => {
143
+ this.log.warn('Secondary connection closed', { ip });
144
+ this.connections.delete(connection_id);
145
+
146
+ // Retry connection after delay
147
+ setTimeout(() => {
148
+ try {
149
+ this.connect_to_secondary(secondary);
150
+ } catch (retry_error) {
151
+ this.log.error('Failed to retry secondary connection', {
152
+ ip,
153
+ error: retry_error.message
154
+ });
155
+ }
156
+ }, 5000);
157
+ });
158
+
159
+ socket.on('data', (data) => {
160
+ try {
161
+ const message = JSON.parse(data.toString());
162
+ this.handle_sync_response(connection_id, message);
163
+ } catch (error) {
164
+ this.log.error('Failed to parse sync response', {
165
+ connection_id,
166
+ error: error.message
167
+ });
168
+ }
169
+ });
170
+
171
+ this.connections.set(connection_id, {
172
+ socket,
173
+ ip,
174
+ connected: true,
175
+ last_sync: null
176
+ });
177
+
178
+ } catch (error) {
179
+ this.log.error('Failed to connect to secondary', {
180
+ ip,
181
+ error: error.message
182
+ });
183
+ }
184
+ }
185
+
186
+ /**
187
+ * Handles sync response from secondary node.
188
+ * @param {string} connection_id - Connection identifier
189
+ * @param {Object} message - Response message from secondary
190
+ */
191
+ handle_sync_response(connection_id, message) {
192
+ const { type, status, sequence, error } = message;
193
+
194
+ if (type !== 'sync_acknowledged') {
195
+ return;
196
+ }
197
+
198
+ if (status === 'success') {
199
+ this.stats.successful_syncs++;
200
+ this.log.debug('Sync acknowledged', { connection_id, sequence });
201
+ } else if (status === 'auth_failed') {
202
+ this.stats.auth_failures++;
203
+ this.log.error('Sync authentication failed', { connection_id, sequence, error });
204
+ } else {
205
+ this.stats.failed_syncs++;
206
+ this.log.error('Sync failed', { connection_id, sequence, error });
207
+ }
208
+ }
209
+
210
+ /**
211
+ * Queues an operation for sync to secondary nodes.
212
+ * @param {string} operation - Operation type
213
+ * @param {string} collection - Collection name
214
+ * @param {Object} data - Operation data
215
+ */
216
+ queue_sync(operation, collection, data) {
217
+ if (!this.is_primary || this.connections.size === 0) {
218
+ return;
219
+ }
220
+
221
+ const sequence = ++this.sequence_number;
222
+
223
+ this.log.debug('Queuing sync operation', {
224
+ operation,
225
+ collection,
226
+ sequence,
227
+ secondary_count: this.connections.size
228
+ });
229
+
230
+ this.stats.total_synced++;
231
+
232
+ // Send immediately to all connected secondaries
233
+ this.send_sync_to_secondaries(operation, collection, data, sequence);
234
+ }
235
+
236
+ /**
237
+ * Sends sync message to all connected secondary nodes.
238
+ * @param {string} operation - Operation type
239
+ * @param {string} collection - Collection name
240
+ * @param {Object} data - Operation data
241
+ * @param {number} sequence - Sequence number
242
+ */
243
+ send_sync_to_secondaries(operation, collection, data, sequence) {
244
+ try {
245
+ const settings = get_settings();
246
+
247
+ if (!settings.api_key) {
248
+ this.log.error('No API_KEY configured for sync operations');
249
+ return;
250
+ }
251
+
252
+ const sync_message = {
253
+ type: 'operation_sync',
254
+ api_key: settings.api_key,
255
+ sequence,
256
+ timestamp: Date.now(),
257
+ operation,
258
+ collection,
259
+ data
260
+ };
261
+
262
+ const encoded_message = encode_message(sync_message);
263
+
264
+ for (const [connection_id, connection] of this.connections) {
265
+ if (!connection.connected || !connection.socket) {
266
+ continue;
267
+ }
268
+
269
+ try {
270
+ connection.socket.write(encoded_message);
271
+ connection.last_sync = Date.now();
272
+
273
+ this.log.debug('Sent sync to secondary', {
274
+ connection_id,
275
+ operation,
276
+ sequence
277
+ });
278
+
279
+ } catch (error) {
280
+ this.log.error('Failed to send sync to secondary', {
281
+ connection_id,
282
+ error: error.message
283
+ });
284
+ }
285
+ }
286
+ } catch (error) {
287
+ this.log.error('Failed to send sync to secondaries', {
288
+ operation,
289
+ sequence,
290
+ error: error.message
291
+ });
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Updates the secondary nodes configuration at runtime.
297
+ * @param {Array<Object>} new_secondary_nodes - New secondary nodes list
298
+ */
299
+ update_secondary_nodes(new_secondary_nodes) {
300
+ this.log.info('Updating secondary nodes configuration', {
301
+ old_count: this.secondary_nodes.length,
302
+ new_count: new_secondary_nodes.length
303
+ });
304
+
305
+ // Close existing connections
306
+ for (const [connection_id, connection] of this.connections) {
307
+ try {
308
+ connection.socket.end();
309
+ } catch (error) {
310
+ this.log.warn('Error closing secondary connection', {
311
+ connection_id,
312
+ error: error.message
313
+ });
314
+ }
315
+ }
316
+
317
+ this.connections.clear();
318
+ this.secondary_nodes = new_secondary_nodes;
319
+
320
+ // Establish new connections
321
+ this.connect_to_secondaries();
322
+ }
323
+
324
+ /**
325
+ * Forces sync of all connected secondaries (health check).
326
+ * @returns {Promise<Object>} Sync status for each secondary
327
+ */
328
+ async force_sync() {
329
+ if (!this.is_primary) {
330
+ throw new Error('Node is not configured as primary');
331
+ }
332
+
333
+ const results = [];
334
+
335
+ for (const [connection_id, connection] of this.connections) {
336
+ try {
337
+ if (connection.connected) {
338
+ results.push({
339
+ connection_id,
340
+ status: 'sync_initiated'
341
+ });
342
+ } else {
343
+ results.push({
344
+ connection_id,
345
+ status: 'not_connected'
346
+ });
347
+ }
348
+ } catch (error) {
349
+ results.push({
350
+ connection_id,
351
+ status: 'error',
352
+ error: error.message
353
+ });
354
+ }
355
+ }
356
+
357
+ return {
358
+ success: true,
359
+ message: 'Force sync initiated',
360
+ results
361
+ };
362
+ }
363
+
364
+ /**
365
+ * Gets current sync status and statistics.
366
+ * @returns {Object} Sync status information
367
+ */
368
+ get_sync_status() {
369
+ const secondary_status = [];
370
+
371
+ for (const [connection_id, connection] of this.connections) {
372
+ secondary_status.push({
373
+ connection_id,
374
+ ip: connection.ip,
375
+ connected: connection.connected,
376
+ last_sync: connection.last_sync
377
+ });
378
+ }
379
+
380
+ return {
381
+ is_primary: this.is_primary,
382
+ secondary_count: this.connections.size,
383
+ stats: this.stats,
384
+ secondaries: secondary_status
385
+ };
386
+ }
387
+
388
+ /**
389
+ * Shuts down the sync manager and closes all connections.
390
+ * @returns {Promise<void>}
391
+ */
392
+ async shutdown() {
393
+ this.log.info('Shutting down simple sync manager');
394
+
395
+ for (const [connection_id, connection] of this.connections) {
396
+ try {
397
+ connection.socket.end();
398
+ } catch (error) {
399
+ this.log.warn('Error closing secondary connection during shutdown', {
400
+ connection_id,
401
+ error: error.message
402
+ });
403
+ }
404
+ }
405
+
406
+ this.connections.clear();
407
+ this.is_primary = false;
408
+
409
+ this.log.info('Simple sync manager shutdown complete');
410
+ }
411
+ }
412
+
413
+ /** @type {SimpleSyncManager|null} Singleton instance */
414
+ let sync_manager_instance = null;
415
+
416
+ /**
417
+ * Gets the sync manager singleton instance.
418
+ * @returns {SimpleSyncManager} Sync manager instance
419
+ */
420
+ export const get_simple_sync_manager = () => {
421
+ if (!sync_manager_instance) {
422
+ sync_manager_instance = new SimpleSyncManager();
423
+ }
424
+ return sync_manager_instance;
425
+ };
426
+
427
+ /**
428
+ * Initializes the sync manager singleton.
429
+ */
430
+ export const initialize_simple_sync_manager = () => {
431
+ const manager = get_simple_sync_manager();
432
+ manager.initialize();
433
+ };
434
+
435
+ /**
436
+ * Shuts down the sync manager singleton.
437
+ * @returns {Promise<void>}
438
+ */
439
+ export const shutdown_simple_sync_manager = async () => {
440
+ if (sync_manager_instance) {
441
+ await sync_manager_instance.shutdown();
442
+ sync_manager_instance = null;
443
+ }
444
+ };