@joystick.js/db-canary 0.0.0-canary.2209
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/.build/getFilesToBuild.js +26 -0
- package/.build/getPlatformSafeFilePath.js +6 -0
- package/.build/getPlatformSafePath.js +6 -0
- package/.build/index.js +88 -0
- package/.build/isWindows.js +3 -0
- package/API_KEY +1 -0
- package/README.md +1821 -0
- package/data/data.mdb +0 -0
- package/data/lock.mdb +0 -0
- package/dist/client/database.js +1 -0
- package/dist/client/index.js +1 -0
- package/dist/server/cluster/index.js +1 -0
- package/dist/server/cluster/master.js +20 -0
- package/dist/server/cluster/worker.js +1 -0
- package/dist/server/index.js +1 -0
- package/dist/server/lib/api_key_manager.js +9 -0
- package/dist/server/lib/auth_manager.js +1 -0
- package/dist/server/lib/auto_index_manager.js +1 -0
- package/dist/server/lib/backup_manager.js +1 -0
- package/dist/server/lib/connection_manager.js +1 -0
- package/dist/server/lib/disk_utils.js +2 -0
- package/dist/server/lib/http_server.js +405 -0
- package/dist/server/lib/index_manager.js +1 -0
- package/dist/server/lib/load_settings.js +1 -0
- package/dist/server/lib/logger.js +1 -0
- package/dist/server/lib/op_types.js +1 -0
- package/dist/server/lib/operation_dispatcher.js +1 -0
- package/dist/server/lib/operations/admin.js +1 -0
- package/dist/server/lib/operations/bulk_write.js +1 -0
- package/dist/server/lib/operations/create_index.js +1 -0
- package/dist/server/lib/operations/delete_one.js +1 -0
- package/dist/server/lib/operations/drop_index.js +1 -0
- package/dist/server/lib/operations/find.js +1 -0
- package/dist/server/lib/operations/find_one.js +1 -0
- package/dist/server/lib/operations/get_indexes.js +1 -0
- package/dist/server/lib/operations/insert_one.js +1 -0
- package/dist/server/lib/operations/update_one.js +1 -0
- package/dist/server/lib/performance_monitor.js +1 -0
- package/dist/server/lib/query_engine.js +1 -0
- package/dist/server/lib/recovery_manager.js +1 -0
- package/dist/server/lib/replication_manager.js +1 -0
- package/dist/server/lib/safe_json_parse.js +1 -0
- package/dist/server/lib/send_response.js +1 -0
- package/dist/server/lib/tcp_protocol.js +1 -0
- package/dist/server/lib/write_forwarder.js +1 -0
- package/dist/server/lib/write_queue.js +1 -0
- package/increment_version.js +3 -0
- package/logs/.013e15b54597d05db4b4b53ecc37b10c92a72927-audit.json +20 -0
- package/logs/.02de550a67ea0f5961faa2dfd458a4d06f59ebd1-audit.json +20 -0
- package/logs/.03494ba24eb3c72214b4068a77d54b8993bee651-audit.json +20 -0
- package/logs/.06309ec60b339be1259a7993dd09c732f8907fbc-audit.json +20 -0
- package/logs/.0663a04dcfa17285661e5e1b8cfa51f41523b210-audit.json +20 -0
- package/logs/.0f06e6c4c9b824622729e13927587479e5060391-audit.json +20 -0
- package/logs/.16ccf58682ecb22b3e3ec63f0da1b7fe9be56528-audit.json +20 -0
- package/logs/.1fa1a5d02f496474b1ab473524c65c984146a9ad-audit.json +20 -0
- package/logs/.2223c0ae3bea6f0d62c62b1d319cc8634856abb7-audit.json +20 -0
- package/logs/.23dc79ffda3e083665e6f5993f59397adcbf4a46-audit.json +20 -0
- package/logs/.28104f49b03906b189eefd1cd462cb46c3c0af22-audit.json +20 -0
- package/logs/.29cdbf13808abe6a0ce70ee2f2efdd680ce3fd8e-audit.json +20 -0
- package/logs/.2a9889afd071f77f41f5170d08703a0afca866b7-audit.json +20 -0
- package/logs/.2acec3d1940a2bbed487528b703ee5948959a599-audit.json +20 -0
- package/logs/.2fb60ff326338c02bfedbcd0e936444e4a216750-audit.json +20 -0
- package/logs/.318fc7a19530d76a345f030f7cad00dda15300e7-audit.json +20 -0
- package/logs/.3cf27043e19085f908cedc7701e6d013463208ee-audit.json +25 -0
- package/logs/.3d90d785415817fc443402843b7c95f8371adc9b-audit.json +20 -0
- package/logs/.4074bca620375f72966fc52dfd439577727671e5-audit.json +20 -0
- package/logs/.40eecf018417ea80a70ea8ec7a3cc9406bc6334b-audit.json +20 -0
- package/logs/.50e974f1ef7c365fca6a1251b2e2c2252914cb5e-audit.json +20 -0
- package/logs/.52cb7d9e4223cf26ba36006ac26b949a97c7923c-audit.json +20 -0
- package/logs/.54befcdb84c15aad980705a31bcc9f555c3577ab-audit.json +20 -0
- package/logs/.57dfb70e22eddb84db2e3c0ceeefac5c0b9baffa-audit.json +20 -0
- package/logs/.5f0b24705a1eaad4eca4968f2d86f91b3f9be683-audit.json +20 -0
- package/logs/.61ba98fdda7db58576b382fee07904e5db1169d6-audit.json +20 -0
- package/logs/.6235017727ef6b199d569a99d6aa8c8e80a1b475-audit.json +20 -0
- package/logs/.63db16193699219489d218a1ddea5dde3750cae4-audit.json +20 -0
- package/logs/.64fb67dfe14149c9eef728d79bf30a54da809c60-audit.json +20 -0
- package/logs/.669137453368987c1f311b5345342527afb54e50-audit.json +20 -0
- package/logs/.7a71f8c89ea28ae266d356aeff6306e876a30fbb-audit.json +20 -0
- package/logs/.7afbaa90fe9dc3a7d682676f9bb79f9a1b1fd9a6-audit.json +20 -0
- package/logs/.7ca29e322cd05327035de850099e7610864f2347-audit.json +20 -0
- package/logs/.83335ab3347e449dae03455a110aaf7f120d4802-audit.json +20 -0
- package/logs/.8c2487b5fd445d2c8e5c483c80b9fa99bbf1ca58-audit.json +20 -0
- package/logs/.8c8b9dc386922c9f3b4c13251af7052aac1d24c0-audit.json +20 -0
- package/logs/.8d6155d94640c4863301ae0fee5e4e7372a21446-audit.json +20 -0
- package/logs/.944a3119a243deea7c8270d5d9e582bb1d0eaa10-audit.json +20 -0
- package/logs/.9816a845c30fb2909f3b26a23eeb3538ebcad5db-audit.json +20 -0
- package/logs/.9dc08784e38b865488177c26d4af5934555e0323-audit.json +20 -0
- package/logs/.9dd27d2e0e454ac0a37600206d1cac5493b0d7ee-audit.json +20 -0
- package/logs/.a3d486feeac7654c59b547de96600e8849a06d4f-audit.json +20 -0
- package/logs/.a5b811f4def22250f86cc18870d7c4573625df22-audit.json +20 -0
- package/logs/.a61648eb5f830e0b6f508ac35e4f8f629d2ad4c7-audit.json +20 -0
- package/logs/.a89016d507045771b4b5a65656944a9c0f1e528b-audit.json +20 -0
- package/logs/.a99bee160a1c590be959af46bacc02724803f691-audit.json +20 -0
- package/logs/.ada7906d6243fd7da802f03d86c4ae5dd9df6236-audit.json +20 -0
- package/logs/.b518339ee942143b6af983af167f5bbb6983b4de-audit.json +20 -0
- package/logs/.b51b124b166d53c9519017856ea610d61d65fabe-audit.json +20 -0
- package/logs/.b7a6aee19f58e55633d5e4a3709041c47dfff975-audit.json +20 -0
- package/logs/.bd7a8a6ba9c55d557a4867ab53f02e3ec2e1553d-audit.json +20 -0
- package/logs/.c1435dafe453b169d6392b25065f3cf4ab6fbb21-audit.json +20 -0
- package/logs/.c17e1ce043109f77dc2f0e2aa290a9d1ed842c03-audit.json +20 -0
- package/logs/.ca62637ce9540e5a38a2fbedb2115febb6ad308a-audit.json +15 -0
- package/logs/.ccee67b9c176967f8977071409a41f5cb5cd6ad4-audit.json +20 -0
- package/logs/.db24043417ea79a6f14cd947476399e53930b48d-audit.json +20 -0
- package/logs/.e0f12acccb57829f5f33712bb2e2607ecd808147-audit.json +20 -0
- package/logs/.e9b6cc33d0bbd2e644c4e2bf44d177f850016557-audit.json +20 -0
- package/logs/.f15291d434808e3bdca7963ccd2e73893be027e6-audit.json +20 -0
- package/logs/.f4bdf9e21ef84f8a3fae3ffb32bbc39275991351-audit.json +15 -0
- package/logs/.fbac3aefac1e81b4230df5aa50667cb90d51024f-audit.json +20 -0
- package/logs/.fcfd495c0a9169db243f4a4f21878ee02b76413c-audit.json +20 -0
- package/logs/admin-2025-09-12.log +580 -0
- package/logs/admin-2025-09-15.log +283 -0
- package/logs/admin-error-2025-09-12.log +22 -0
- package/logs/admin-error-2025-09-15.log +10 -0
- package/logs/api_key_manager-2025-09-12.log +658 -0
- package/logs/api_key_manager-2025-09-15.log +295 -0
- package/logs/api_key_manager-error-2025-09-12.log +0 -0
- package/logs/api_key_manager-error-2025-09-15.log +0 -0
- package/logs/auth_manager-2025-09-12.log +4432 -0
- package/logs/auth_manager-2025-09-15.log +2000 -0
- package/logs/auth_manager-error-2025-09-12.log +11 -0
- package/logs/auth_manager-error-2025-09-15.log +5 -0
- package/logs/auto_index_manager-2025-09-12.log +84 -0
- package/logs/auto_index_manager-2025-09-15.log +45 -0
- package/logs/auto_index_manager-error-2025-09-12.log +6 -0
- package/logs/auto_index_manager-error-2025-09-15.log +0 -0
- package/logs/backup_manager-2025-09-12.log +198 -0
- package/logs/backup_manager-2025-09-15.log +90 -0
- package/logs/backup_manager-error-2025-09-12.log +198 -0
- package/logs/backup_manager-error-2025-09-15.log +90 -0
- package/logs/bulk_write-2025-09-12.log +66 -0
- package/logs/bulk_write-2025-09-15.log +38 -0
- package/logs/bulk_write-error-2025-09-12.log +0 -0
- package/logs/bulk_write-error-2025-09-15.log +0 -0
- package/logs/connection_manager-2025-09-12.log +2412 -0
- package/logs/connection_manager-2025-09-15.log +1132 -0
- package/logs/connection_manager-error-2025-09-12.log +0 -0
- package/logs/connection_manager-error-2025-09-15.log +0 -0
- package/logs/create_index-2025-09-12.log +302 -0
- package/logs/create_index-2025-09-15.log +158 -0
- package/logs/create_index-error-2025-09-12.log +30 -0
- package/logs/create_index-error-2025-09-15.log +13 -0
- package/logs/delete_one-2025-09-12.log +73 -0
- package/logs/delete_one-2025-09-15.log +43 -0
- package/logs/delete_one-error-2025-09-12.log +0 -0
- package/logs/delete_one-error-2025-09-15.log +0 -0
- package/logs/disk_utils-2025-09-12.log +4954 -0
- package/logs/disk_utils-2025-09-15.log +2446 -0
- package/logs/disk_utils-error-2025-09-12.log +0 -0
- package/logs/disk_utils-error-2025-09-15.log +0 -0
- package/logs/drop_index-2025-09-12.log +41 -0
- package/logs/drop_index-2025-09-15.log +23 -0
- package/logs/drop_index-error-2025-09-12.log +11 -0
- package/logs/drop_index-error-2025-09-15.log +5 -0
- package/logs/find-2025-09-12.log +1050 -0
- package/logs/find-2025-09-15.log +592 -0
- package/logs/find-error-2025-09-12.log +1 -0
- package/logs/find-error-2025-09-15.log +0 -0
- package/logs/find_one-2025-09-12.log +425 -0
- package/logs/find_one-2025-09-15.log +264 -0
- package/logs/find_one-error-2025-09-12.log +5 -0
- package/logs/find_one-error-2025-09-15.log +0 -0
- package/logs/get_indexes-2025-09-12.log +84 -0
- package/logs/get_indexes-2025-09-15.log +56 -0
- package/logs/get_indexes-error-2025-09-12.log +6 -0
- package/logs/get_indexes-error-2025-09-15.log +0 -0
- package/logs/http_server-2025-09-12.log +2772 -0
- package/logs/http_server-2025-09-15.log +1276 -0
- package/logs/http_server-error-2025-09-12.log +212 -0
- package/logs/http_server-error-2025-09-15.log +44 -0
- package/logs/index_manager-2025-09-12.log +5031 -0
- package/logs/index_manager-2025-09-15.log +2909 -0
- package/logs/index_manager-error-2025-09-12.log +80 -0
- package/logs/index_manager-error-2025-09-15.log +38 -0
- package/logs/insert_one-2025-09-12.log +2181 -0
- package/logs/insert_one-2025-09-15.log +1293 -0
- package/logs/insert_one-error-2025-09-12.log +0 -0
- package/logs/insert_one-error-2025-09-15.log +0 -0
- package/logs/master-2025-09-12.log +1882 -0
- package/logs/master-2025-09-15.log +910 -0
- package/logs/master-error-2025-09-12.log +80 -0
- package/logs/master-error-2025-09-15.log +0 -0
- package/logs/operation_dispatcher-2025-09-12.log +751 -0
- package/logs/operation_dispatcher-2025-09-15.log +359 -0
- package/logs/operation_dispatcher-error-2025-09-12.log +33 -0
- package/logs/operation_dispatcher-error-2025-09-15.log +11 -0
- package/logs/performance_monitor-2025-09-12.log +14889 -0
- package/logs/performance_monitor-2025-09-15.log +6803 -0
- package/logs/performance_monitor-error-2025-09-12.log +0 -0
- package/logs/performance_monitor-error-2025-09-15.log +0 -0
- package/logs/query_engine-2025-09-12.log +5310 -0
- package/logs/query_engine-2025-09-15.log +2639 -0
- package/logs/query_engine-error-2025-09-12.log +0 -0
- package/logs/query_engine-error-2025-09-15.log +0 -0
- package/logs/recovery_manager-2025-09-12.log +462 -0
- package/logs/recovery_manager-2025-09-15.log +210 -0
- package/logs/recovery_manager-error-2025-09-12.log +22 -0
- package/logs/recovery_manager-error-2025-09-15.log +10 -0
- package/logs/replication-2025-09-12.log +1923 -0
- package/logs/replication-2025-09-15.log +917 -0
- package/logs/replication-error-2025-09-12.log +33 -0
- package/logs/replication-error-2025-09-15.log +15 -0
- package/logs/server-2025-09-12.log +2601 -0
- package/logs/server-2025-09-15.log +1191 -0
- package/logs/server-error-2025-09-12.log +0 -0
- package/logs/server-error-2025-09-15.log +0 -0
- package/logs/tcp_protocol-2025-09-12.log +22 -0
- package/logs/tcp_protocol-2025-09-15.log +10 -0
- package/logs/tcp_protocol-error-2025-09-12.log +22 -0
- package/logs/tcp_protocol-error-2025-09-15.log +10 -0
- package/logs/test-2025-09-12.log +0 -0
- package/logs/test-2025-09-15.log +0 -0
- package/logs/test-error-2025-09-12.log +0 -0
- package/logs/test-error-2025-09-15.log +0 -0
- package/logs/update_one-2025-09-12.log +173 -0
- package/logs/update_one-2025-09-15.log +118 -0
- package/logs/update_one-error-2025-09-12.log +0 -0
- package/logs/update_one-error-2025-09-15.log +0 -0
- package/logs/worker-2025-09-12.log +1457 -0
- package/logs/worker-2025-09-15.log +695 -0
- package/logs/worker-error-2025-09-12.log +0 -0
- package/logs/worker-error-2025-09-15.log +0 -0
- package/logs/write_forwarder-2025-09-12.log +1956 -0
- package/logs/write_forwarder-2025-09-15.log +932 -0
- package/logs/write_forwarder-error-2025-09-12.log +66 -0
- package/logs/write_forwarder-error-2025-09-15.log +30 -0
- package/logs/write_queue-2025-09-12.log +612 -0
- package/logs/write_queue-2025-09-15.log +301 -0
- package/logs/write_queue-error-2025-09-12.log +184 -0
- package/logs/write_queue-error-2025-09-15.log +83 -0
- package/package.json +48 -0
- package/prompts/01-core-infrastructure.md +56 -0
- package/prompts/02-secondary-indexing.md +65 -0
- package/prompts/03-write-serialization.md +63 -0
- package/prompts/04-enhanced-authentication.md +75 -0
- package/prompts/05-comprehensive-admin-operations.md +75 -0
- package/prompts/06-backup-and-restore-system.md +106 -0
- package/prompts/07-production-safety-features.md +107 -0
- package/prompts/08-tcp-client-library.md +121 -0
- package/prompts/09-api-method-chaining.md +134 -0
- package/prompts/10-automatic-index-creation.md +223 -0
- package/prompts/11-operation-naming-consistency.md +268 -0
- package/prompts/12-tcp-replication-system.md +333 -0
- package/prompts/13-master-read-write-operations.md +57 -0
- package/prompts/14-index-upsert-operations.md +68 -0
- package/prompts/15-client-api-return-types.md +81 -0
- package/prompts/16-server-setup-ui.md +97 -0
- package/prompts/17-emergency-password-change.md +108 -0
- package/prompts/18-joystick-framework-integration.md +116 -0
- package/prompts/19-api-key-authentication-system.md +137 -0
- package/prompts/20-configurable-server-port.md +105 -0
- package/prompts/21-multi-database-support.md +161 -0
- package/prompts/FULL_TEXT_SEARCH.md +293 -0
- package/prompts/PROMPTS.md +158 -0
- package/prompts/README.md +221 -0
- package/prompts/TYPESCRIPT_GENERATION.md +179 -0
- package/src/client/database.js +166 -0
- package/src/client/index.js +752 -0
- package/src/server/cluster/index.js +53 -0
- package/src/server/cluster/master.js +774 -0
- package/src/server/cluster/worker.js +537 -0
- package/src/server/index.js +540 -0
- package/src/server/lib/api_key_manager.js +473 -0
- package/src/server/lib/auth_manager.js +375 -0
- package/src/server/lib/auto_index_manager.js +681 -0
- package/src/server/lib/backup_manager.js +650 -0
- package/src/server/lib/connection_manager.js +218 -0
- package/src/server/lib/disk_utils.js +118 -0
- package/src/server/lib/http_server.js +1165 -0
- package/src/server/lib/index_manager.js +756 -0
- package/src/server/lib/load_settings.js +143 -0
- package/src/server/lib/logger.js +135 -0
- package/src/server/lib/op_types.js +29 -0
- package/src/server/lib/operation_dispatcher.js +268 -0
- package/src/server/lib/operations/admin.js +808 -0
- package/src/server/lib/operations/bulk_write.js +367 -0
- package/src/server/lib/operations/create_index.js +68 -0
- package/src/server/lib/operations/delete_one.js +114 -0
- package/src/server/lib/operations/drop_index.js +58 -0
- package/src/server/lib/operations/find.js +340 -0
- package/src/server/lib/operations/find_one.js +319 -0
- package/src/server/lib/operations/get_indexes.js +52 -0
- package/src/server/lib/operations/insert_one.js +113 -0
- package/src/server/lib/operations/update_one.js +225 -0
- package/src/server/lib/performance_monitor.js +313 -0
- package/src/server/lib/query_engine.js +243 -0
- package/src/server/lib/recovery_manager.js +388 -0
- package/src/server/lib/replication_manager.js +727 -0
- package/src/server/lib/safe_json_parse.js +21 -0
- package/src/server/lib/send_response.js +47 -0
- package/src/server/lib/tcp_protocol.js +130 -0
- package/src/server/lib/write_forwarder.js +636 -0
- package/src/server/lib/write_queue.js +335 -0
- package/test_data/data.mdb +0 -0
- package/test_data/lock.mdb +0 -0
- package/tests/client/index.test.js +1232 -0
- package/tests/server/cluster/cluster.test.js +248 -0
- package/tests/server/cluster/master_read_write_operations.test.js +577 -0
- package/tests/server/index.test.js +651 -0
- package/tests/server/integration/authentication_integration.test.js +294 -0
- package/tests/server/integration/auto_indexing_integration.test.js +268 -0
- package/tests/server/integration/backup_integration.test.js +513 -0
- package/tests/server/integration/indexing_integration.test.js +126 -0
- package/tests/server/integration/production_safety_integration.test.js +358 -0
- package/tests/server/integration/replication_integration.test.js +227 -0
- package/tests/server/integration/write_serialization_integration.test.js +246 -0
- package/tests/server/lib/api_key_manager.test.js +516 -0
- package/tests/server/lib/auth_manager.test.js +317 -0
- package/tests/server/lib/auto_index_manager.test.js +275 -0
- package/tests/server/lib/backup_manager.test.js +238 -0
- package/tests/server/lib/connection_manager.test.js +221 -0
- package/tests/server/lib/disk_utils.test.js +63 -0
- package/tests/server/lib/http_server.test.js +389 -0
- package/tests/server/lib/index_manager.test.js +301 -0
- package/tests/server/lib/load_settings.test.js +107 -0
- package/tests/server/lib/load_settings_port_config.test.js +243 -0
- package/tests/server/lib/logger.test.js +282 -0
- package/tests/server/lib/operations/admin.test.js +638 -0
- package/tests/server/lib/operations/bulk_write.test.js +128 -0
- package/tests/server/lib/operations/create_index.test.js +138 -0
- package/tests/server/lib/operations/delete_one.test.js +52 -0
- package/tests/server/lib/operations/drop_index.test.js +72 -0
- package/tests/server/lib/operations/find.test.js +93 -0
- package/tests/server/lib/operations/find_one.test.js +91 -0
- package/tests/server/lib/operations/get_indexes.test.js +87 -0
- package/tests/server/lib/operations/insert_one.test.js +42 -0
- package/tests/server/lib/operations/update_one.test.js +89 -0
- package/tests/server/lib/performance_monitor.test.js +185 -0
- package/tests/server/lib/query_engine.test.js +46 -0
- package/tests/server/lib/recovery_manager.test.js +414 -0
- package/tests/server/lib/replication_manager.test.js +202 -0
- package/tests/server/lib/safe_json_parse.test.js +45 -0
- package/tests/server/lib/send_response.test.js +155 -0
- package/tests/server/lib/tcp_protocol.test.js +169 -0
- package/tests/server/lib/write_forwarder.test.js +258 -0
- package/tests/server/lib/write_queue.test.js +255 -0
- package/tsconfig.json +30 -0
- package/types/client/index.d.ts +447 -0
- package/types/server/cluster/index.d.ts +28 -0
- package/types/server/cluster/master.d.ts +115 -0
- package/types/server/cluster/worker.d.ts +1 -0
- package/types/server/lib/auth_manager.d.ts +13 -0
- package/types/server/lib/backup_manager.d.ts +43 -0
- package/types/server/lib/connection_manager.d.ts +15 -0
- package/types/server/lib/disk_utils.d.ts +3 -0
- package/types/server/lib/index_manager.d.ts +24 -0
- package/types/server/lib/load_settings.d.ts +4 -0
- package/types/server/lib/logger.d.ts +44 -0
- package/types/server/lib/op_types.d.ts +6 -0
- package/types/server/lib/performance_monitor.d.ts +68 -0
- package/types/server/lib/query_engine.d.ts +10 -0
- package/types/server/lib/safe_json_parse.d.ts +7 -0
- package/types/server/lib/send_response.d.ts +3 -0
- package/types/server/lib/tcp_protocol.d.ts +12 -0
- package/types/server/lib/write_queue.d.ts +2 -0
package/data/data.mdb
ADDED
|
Binary file
|
package/data/lock.mdb
ADDED
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
class n{constructor(t,e){this.client=t,this.database_name=e}collection(t){const e=this.client.constructor.Collection;return new e(this.client,this.database_name,t)}async list_collections(){return this.client.send_request("admin",{admin_action:"list_collections",database:this.database_name})}async get_stats(){return this.client.send_request("admin",{admin_action:"get_database_stats",database:this.database_name})}async drop_database(){return this.client.send_request("admin",{admin_action:"drop_database",database:this.database_name})}async create_collection(t,e={}){return this.client.send_request("admin",{admin_action:"create_collection",database:this.database_name,collection:t,options:e})}async list_documents(t){return this.client.send_request("admin",{admin_action:"list_documents",database:this.database_name,collection:t})}async get_document(t,e){return this.client.send_request("admin",{admin_action:"get_document",database:this.database_name,collection:t,document_id:e})}async query_documents(t,e){return this.client.send_request("admin",{admin_action:"query_documents",database:this.database_name,collection:t,filter:e})}async insert_document(t,e){return this.client.send_request("admin",{admin_action:"insert_document",database:this.database_name,collection:t,document:e})}async update_document(t,e,a){return this.client.send_request("admin",{admin_action:"update_document",database:this.database_name,collection:t,document_id:e,update:a})}async delete_document(t,e){return this.client.send_request("admin",{admin_action:"delete_document",database:this.database_name,collection:t,document_id:e})}}var i=n;export{i as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import h from"net";import{EventEmitter as u}from"events";import{encode as d,decode as l}from"msgpackr";import m from"./database.js";const p=n=>{const e=d(n,{useFloat32:!1,int64AsType:"number",mapsAsObjects:!0}),t=Buffer.allocUnsafe(4);return t.writeUInt32BE(e.length,0),Buffer.concat([t,e])},f=()=>{let n=Buffer.alloc(0),e=null;return{parse_messages:i=>{n=Buffer.concat([n,i]);const r=[];for(;n.length>0;){if(e===null){if(n.length<4)break;e=n.readUInt32BE(0),n=n.slice(4)}if(n.length<e)break;const a=n.slice(0,e);n=n.slice(e),e=null;try{const c=l(a,{useFloat32:!1,int64AsType:"number",mapsAsObjects:!0});r.push(c)}catch(c){throw new Error(`Invalid message format: ${c.message}`)}}return r},reset:()=>{n=Buffer.alloc(0),e=null}}};class _ extends u{constructor(e={}){super(),this.host=e.host||"localhost",this.port=e.port||1983,this.password=e.password||null,this.timeout=e.timeout||5e3,this.reconnect=e.reconnect!==!1,this.max_reconnect_attempts=e.max_reconnect_attempts||10,this.reconnect_delay=e.reconnect_delay||1e3,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=[],e.auto_connect!==!1&&this.connect()}connect(){if(this.is_connecting||this.is_connected)return;this.is_connecting=!0,this.socket=new h.Socket,this.message_parser=f();const e=setTimeout(()=>{this.socket&&!this.is_connected&&(this.socket.destroy(),this.handle_connection_error(new Error("Connection timeout")))},this.timeout);this.socket.connect(this.port,this.host,()=>{clearTimeout(e),this.is_connected=!0,this.is_connecting=!1,this.reconnect_attempts=0,this.emit("connect"),this.password&&this.authenticate()}),this.socket.on("data",t=>{try{const s=this.message_parser.parse_messages(t);for(const i of s)this.handle_message(i)}catch(s){this.emit("error",new Error(`Message parsing failed: ${s.message}`))}}),this.socket.on("error",t=>{clearTimeout(e),this.handle_connection_error(t)}),this.socket.on("close",()=>{clearTimeout(e),this.handle_disconnect()})}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.is_authenticated=!0,this.emit("authenticated"),this.process_request_queue();else throw new Error("Authentication failed")}catch(e){this.emit("error",new Error(`Authentication error: ${e.message}`)),this.disconnect()}}handle_message(e){if(this.pending_requests.size>0){const[t,{resolve:s,reject:i,timeout:r}]=this.pending_requests.entries().next().value;if(clearTimeout(r),this.pending_requests.delete(t),e.ok===1||e.ok===!0)s(e);else if(e.ok===0||e.ok===!1){const a=typeof e.error=="string"?e.error:JSON.stringify(e.error)||"Operation failed";i(new Error(a))}else s(e)}else this.emit("response",e)}handle_connection_error(e){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();for(const[t,{reject:s,timeout:i}]of this.pending_requests)clearTimeout(i),s(new Error("Connection lost"));this.pending_requests.clear(),this.emit("error",e),this.reconnect&&this.reconnect_attempts<this.max_reconnect_attempts?this.schedule_reconnect():this.emit("disconnect")}handle_disconnect(){this.is_connected=!1,this.is_authenticated=!1,this.is_connecting=!1,this.socket&&(this.socket.removeAllListeners(),this.socket=null),this.message_parser&&this.message_parser.reset();for(const[e,{reject:t,timeout:s}]of this.pending_requests)clearTimeout(s),t(new Error("Connection closed"));this.pending_requests.clear(),this.reconnect&&this.reconnect_attempts<this.max_reconnect_attempts?this.schedule_reconnect():this.emit("disconnect")}schedule_reconnect(){this.reconnect_attempts++;const e=Math.min(this.reconnect_delay*Math.pow(2,this.reconnect_attempts-1),3e4);this.emit("reconnecting",{attempt:this.reconnect_attempts,delay:e}),this.reconnect_timeout=setTimeout(()=>{this.connect()},e)}send_request(e,t={},s=!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.is_connected||e!=="authentication"&&e!=="setup"&&e!=="ping"&&!this.is_authenticated)if(s){this.request_queue.push(o);return}else{r(new Error("Not connected or authenticated"));return}this.send_request_now(o)})}send_request_now(e){const{message:t,resolve:s,reject:i,request_id:r}=e,a=setTimeout(()=>{this.pending_requests.delete(r),i(new Error("Request timeout"))},this.timeout);this.pending_requests.set(r,{resolve:s,reject:i,timeout:a});try{const c=p(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}db(e){return new m(this,e)}async list_databases(){return this.send_request("admin",{admin_action:"list_databases"})}}class g{constructor(e,t,s){this.client=e,this.database_name=t,this.collection_name=s}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 update_one(e,t,s={}){return this.client.send_request("update_one",{database:this.database_name,collection:this.collection_name,filter:e,update:t,options:s})}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 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})}}_.Collection=g;const q={client:n=>new _(n)};var x=q;export{x as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import t from"cluster";import o from"./master.js";const e=(r={})=>{if(t.isMaster||t.isPrimary){const s=new o(r);return process.on("SIGTERM",async()=>{console.log("[cluster] Received SIGTERM, shutting down gracefully..."),await s.shutdown(),process.exit(0)}),process.on("SIGINT",async()=>{console.log("[cluster] Received SIGINT, shutting down gracefully..."),await s.shutdown(),process.exit(0)}),s.start(),s}else import("./worker.js")};process.argv[1].includes("cluster/index.js")&&e();var n=e;export{n as default,e as start_cluster};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import a from"cluster";import d from"os";import{EventEmitter as l}from"events";import{writeFileSync as u}from"fs";import{load_settings as _,get_port_configuration as w}from"../lib/load_settings.js";import{restore_backup as p,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/bulk_write.js";import W from"../lib/operations/find_one.js";import F from"../lib/operations/find.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 z,is_setup_required as A}from"../lib/http_server.js";class I extends l{constructor(t={}){super(),this.workers=new Map,this.write_queue=[],this.processing_writes=!1,this.authenticated_sessions=new Map,this.worker_count=t.worker_count||d.cpus().length,this.port=t.port||1983,this.settings_file=t.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:e}=m("master");this.log=e({port:this.port,worker_count:this.worker_count,master_id:this.master_id}),this.setup_master()}setup_master(){a.setupPrimary({exec:new URL("./index.js",import.meta.url).pathname,args:[],silent:!1}),a.on("exit",(t,e,r)=>{this.log.warn("Worker died",{worker_pid:t.process.pid,exit_code:e,signal:r}),this.handle_worker_death(t)}),a.on("message",(t,e)=>{this.handle_worker_message(t,e)})}async start(){const t=Date.now();try{if(this.settings=_(this.settings_file),this.log.info("Settings loaded successfully",{settings_file:this.settings_file}),k(),D(),this.log.info("Database and auth manager initialized"),this.settings?.restore_from)try{this.log.info("Startup restore requested",{backup_filename:this.settings.restore_from});const r=await p(this.settings.restore_from);this.log.info("Startup restore completed",{backup_filename:this.settings.restore_from,duration_ms:r.duration_ms});const s={...this.settings};delete s.restore_from,u(this.settings_file,JSON.stringify(s,null,2)),this.settings=_(this.settings_file),this.log.info("Removed restore_from from settings after successful restore")}catch(r){this.log.error("Startup restore failed",{backup_filename:this.settings.restore_from,error:r.message}),this.log.info("Continuing with existing database after restore failure")}if(this.settings?.s3)try{g(),this.log.info("Backup scheduling started")}catch(r){this.log.warn("Failed to start backup scheduling",{error:r.message})}if(A())try{const{http_port:r}=w();await R(r)&&this.log.info("HTTP setup server started",{http_port:r})}catch(r){this.log.warn("Failed to start HTTP setup server",{error:r.message})}for(let r=0;r<this.worker_count;r++)this.spawn_worker();const e=Date.now()-t;this.log.info("Master process started successfully",{workers_spawned:this.worker_count,startup_duration_ms:e})}catch(e){this.log.error("Failed to start master process",{error:e.message}),process.exit(1)}}spawn_worker(){const t=Date.now();this.log.info("Spawning worker");const e=a.fork({WORKER_PORT:this.port,WORKER_SETTINGS:JSON.stringify(this.settings)});this.workers.set(e.id,{worker:e,connections:0,last_heartbeat:Date.now(),status:"starting"});const r=Date.now()-t;return this.log.info("Worker spawned successfully",{worker_id:e.id,worker_pid:e.process.pid,spawn_duration_ms:r}),e}handle_worker_death(t){this.workers.delete(t.id),this.shutting_down||(this.log.info("Respawning worker after death",{dead_worker_id:t.id,respawn_delay_ms:1e3}),setTimeout(()=>{this.spawn_worker()},1e3))}handle_worker_message(t,e){switch(e.type){case"worker_ready":this.handle_worker_ready_for_config(t,e);break;case"server_ready":this.handle_worker_server_ready(t,e);break;case"write_request":this.handle_write_request(t,e);break;case"auth_request":this.handle_auth_request(t,e);break;case"setup_request":this.handle_setup_request(t,e);break;case"connection_count":this.update_worker_connections(t,e);break;case"heartbeat":this.handle_worker_heartbeat(t,e);break;default:this.log.warn("Unknown message type received",{message_type:e.type,worker_id:t.id})}}handle_worker_ready_for_config(t,e){this.log.info("Worker ready for config, sending configuration",{worker_id:t.id,worker_pid:t.process.pid,master_id:this.master_id}),t.send({type:"config",data:{port:this.port,settings:this.settings,master_id:this.master_id}})}handle_worker_server_ready(t,e){const r=this.workers.get(t.id);r&&(r.status="ready",this.log.info("Worker server ready",{worker_id:t.id,worker_pid:t.process.pid}))}async handle_write_request(t,e){if(this.shutting_down){t.send({type:"write_response",data:{write_id:e.data.write_id,success:!1,error:"Server is shutting down"}});return}const{write_id:r,op_type:s,data:o,socket_id:i}=e.data;try{const n={write_id:r,worker_id:t.id,op_type:s,data:o,socket_id:i,timestamp:Date.now()};this.write_queue.push(n),this.process_write_queue()}catch(n){t.send({type:"write_response",data:{write_id:r,success:!1,error:n.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 t=this.write_queue.shift();await this.execute_write_operation(t)}this.processing_writes=!1,this.shutting_down&&this.write_queue.length===0&&this.emit("writes_completed")}}async execute_write_operation(t){const{write_id:e,worker_id:r,op_type:s,data:o,socket_id:i}=t,n=this.workers.get(r);if(!n){this.log.error("Worker not found for write operation",{worker_id:r});return}try{const c=await this.perform_database_operation(s,o);n.worker.send({type:"write_response",data:{write_id:e,success:!0,result:c}}),this.broadcast_write_notification(s,o,r)}catch(c){this.log.error("Write operation failed",{write_id:e,op_type:s,worker_id:r,error_message:c.message}),n.worker.send({type:"write_response",data:{write_id:e,success:!1,error:c.message}})}}async perform_database_operation(t,e){const r=Date.now();this.log.info("Executing database operation",{op_type:t});try{let s;const o=e.database||"default";switch(t){case"insert_one":s=await x(o,e.collection,e.document,e.options);break;case"update_one":s=await E(o,e.collection,e.filter,e.update,e.options);break;case"delete_one":s=await T(o,e.collection,e.filter,e.options);break;case"bulk_write":s=await S(o,e.collection,e.operations,e.options);break;case"find_one":s=await W(o,e.collection,e.filter,e.options);break;case"find":s=await F(o,e.collection,e.filter,e.options);break;case"create_index":s=await N(o,e.collection,e.field,e.options);break;case"drop_index":s=await P(o,e.collection,e.field);break;case"get_indexes":s=await O(o,e.collection);break;default:throw new Error(`Unsupported database operation: ${t}`)}const i=Date.now()-r;return this.log.log_operation(t,i,{result:s}),["find_one","find","get_indexes"].includes(t)||setImmediate(()=>y()),s}catch(s){const o=Date.now()-r;throw this.log.error("Database operation failed",{op_type:t,duration_ms:o,error_message:s.message}),s}}broadcast_write_notification(t,e,r){const s={type:"write_notification",data:{op_type:t,data:e,timestamp:Date.now()}};for(const[o,i]of this.workers)o!==r&&i.status==="ready"&&i.worker.send(s)}async handle_auth_request(t,e){const{auth_id:r,socket_id:s,password:o}=e.data;try{const i=await q(o,"cluster_client");i&&this.authenticated_sessions.set(s,{authenticated_at:Date.now(),worker_id:t.id}),t.send({type:"auth_response",data:{auth_id:r,success:i,message:i?"Authentication successful":"Authentication failed"}})}catch(i){t.send({type:"auth_response",data:{auth_id:r,success:!1,message:`Authentication error: ${i.message}`}})}}handle_setup_request(t,e){const{setup_id:r,socket_id:s}=e.data;try{const o=v(),i=`===
|
|
2
|
+
JoystickDB Setup
|
|
3
|
+
|
|
4
|
+
Your database has been setup. Follow the instructions below carefully to avoid issues.
|
|
5
|
+
|
|
6
|
+
Password:
|
|
7
|
+
${o}
|
|
8
|
+
|
|
9
|
+
Store this password in your environment settings or another secure location. When connecting a client, make sure to provide this password via the client's options object like this:
|
|
10
|
+
|
|
11
|
+
import joystickdb from '@joystickdb/client';
|
|
12
|
+
|
|
13
|
+
const client = joystickdb.client({
|
|
14
|
+
host: 'localhost',
|
|
15
|
+
port: 1983,
|
|
16
|
+
password: '${o}'
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
await client.ping();
|
|
20
|
+
===`;t.send({type:"setup_response",data:{setup_id:r,success:!0,password:o,instructions:i,message:"Authentication setup completed successfully"}})}catch(o){t.send({type:"setup_response",data:{setup_id:r,success:!1,error:o.message}})}}update_worker_connections(t,e){const r=this.workers.get(t.id);r&&(r.connections=e.data.count)}handle_worker_heartbeat(t,e){const r=this.workers.get(t.id);r&&(r.last_heartbeat=Date.now())}get_cluster_stats(){const t={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[e,r]of this.workers)t.total_connections+=r.connections,t.workers.push({id:e,pid:r.worker.process.pid,connections:r.connections,status:r.status,last_heartbeat:r.last_heartbeat});return t}async shutdown(){const t=Date.now();this.log.info("Initiating graceful shutdown"),this.shutting_down=!0;try{await z(),this.log.info("HTTP server stopped")}catch(s){this.log.warn("Failed to stop HTTP server",{error:s.message})}try{f(),this.log.info("Backup scheduling stopped")}catch(s){this.log.warn("Failed to stop backup scheduling",{error:s.message})}for(const[s,o]of this.workers)try{o.worker.send({type:"shutdown"})}catch(i){this.log.warn("Error sending shutdown signal to worker",{worker_id:s,error:i.message})}this.write_queue.length>0&&(this.log.info("Waiting for pending writes to complete",{pending_writes:this.write_queue.length}),await new Promise(s=>{const o=setTimeout(()=>{this.log.warn("Timeout waiting for writes to complete, proceeding with shutdown"),s()},process.env.NODE_ENV==="test"?1e3:5e3);this.once("writes_completed",()=>{clearTimeout(o),s()})})),this.log.info("All writes completed, disconnecting workers");for(const[s,o]of this.workers)try{o.worker.disconnect()}catch(i){this.log.warn("Error disconnecting worker",{worker_id:s,error:i.message})}const e=process.env.NODE_ENV==="test"?500:3e3;await new Promise(s=>{const o=setTimeout(()=>{for(const[n,c]of this.workers){this.log.warn("Force killing worker after timeout",{worker_id:n});try{c.worker.kill("SIGKILL")}catch(h){this.log.warn("Error force killing worker",{worker_id:n,error:h.message})}}this.workers.clear(),s()},e),i=()=>{this.workers.size===0?(clearTimeout(o),s()):setTimeout(i,50)};i()});try{b(),this.log.info("Database cleanup completed")}catch(s){this.log.warn("Error during database cleanup",{error:s.message})}if(this.authenticated_sessions.clear(),this.write_queue.length=0,this.pending_writes.clear(),process.env.NODE_ENV==="test")try{for(const s in a.workers){const o=a.workers[s];if(o&&!o.isDead()){this.log.info("Force killing remaining cluster worker",{worker_id:s,worker_pid:o.process.pid});try{o.kill("SIGKILL")}catch(i){this.log.warn("Error force killing remaining worker",{worker_id:s,error:i.message})}}}for(const s in a.workers)delete a.workers[s];a.removeAllListeners(),this.log.info("Aggressive cluster cleanup completed for test environment")}catch(s){this.log.warn("Error during aggressive cluster cleanup",{error:s.message})}const r=Date.now()-t;this.log.info("Shutdown complete",{shutdown_duration_ms:r})}}var oe=I;export{oe as default};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import h from"net";import p from"../lib/op_types.js";import{send_success as u,send_error as d,send_message as l}from"../lib/send_response.js";import{shutdown_write_queue as g}from"../lib/write_queue.js";import{create_message_parser as w,encode_message as c}from"../lib/tcp_protocol.js";import m from"../lib/logger.js";import{initialize_database as f,cleanup_database as y}from"../lib/query_engine.js";import{handle_admin_operation as b,handle_ping_operation as v}from"../lib/operation_dispatcher.js";class k{constructor(){this.server=null,this.connections=new Map,this.connection_count=0,this.settings=null,this.port=null,this.write_id_counter=0,this.pending_writes=new Map,this.authenticated_clients=new Set,this.heartbeat_interval=null;const{create_context_logger:e}=m("worker");this.log=e({worker_pid:process.pid}),this.setup_worker()}setup_worker(){process.on("message",e=>{this.handle_master_message(e)}),process.on("SIGTERM",()=>{this.shutdown()}),process.on("SIGINT",()=>{this.shutdown()}),this.send_heartbeat(),this.heartbeat_interval=setInterval(()=>{this.send_heartbeat()},5e3),process.connected&&process.send({type:"worker_ready"})}handle_master_message(e){switch(e.type){case"config":this.handle_config(e);break;case"write_response":this.handle_write_response(e);break;case"auth_response":this.handle_auth_response(e);break;case"setup_response":this.handle_setup_response(e);break;case"write_notification":this.handle_write_notification(e);break;case"shutdown":this.shutdown();break;default:this.log.warn("Unknown message type received from master",{message_type:e.type})}}handle_config(e){const t=e.data.master_id;if(this.master_id&&this.master_id!==t){this.log.info("Worker already configured by different master, ignoring config message",{current_master_id:this.master_id,incoming_master_id:t,current_port:this.port,new_port:e.data.port});return}if(this.port!==null&&this.master_id===t){this.log.info("Worker already configured by same master, ignoring duplicate config message",{master_id:t,current_port:this.port,new_port:e.data.port});return}this.log.info("Received config message",{port:e.data.port,master_id:t}),this.port=e.data.port,this.settings=e.data.settings,this.master_id=t;try{f(),this.log.info("Database initialized in worker process")}catch(s){this.log.error("Failed to initialize database in worker process",{error:s.message})}this.log.info("Starting server",{port:this.port}),this.start_server()}start_server(){this.server=h.createServer(e=>{this.handle_connection(e)}),this.server.listen(this.port,()=>{this.log.info("Server listening",{port:this.port}),process.connected&&process.send({type:"server_ready"})}),this.server.on("error",e=>{this.log.error("Server error",{error:e.message})})}handle_connection(e){const t=`${process.pid}_${Date.now()}_${Math.random()}`;e.id=t,e.message_parser=w(),this.connections.set(t,e),this.connection_count++,this.update_connection_count(),e.on("data",s=>{this.handle_socket_data(e,s)}),e.on("end",()=>{this.handle_socket_end(e)}),e.on("error",s=>{this.log.error("Socket error",{socket_id:t,error_message:s.message}),this.handle_socket_end(e)})}handle_socket_data(e,t){try{const s=e.message_parser.parse_messages(t);for(const r of s){const a=r,i=a?.op||null;if(!i){d(e,{message:"Missing operation type"});continue}if(!this.check_op_type(i)){d(e,{message:"Invalid operation type"});continue}this.route_operation(e,i,a?.data||{})}}catch(s){this.log.error("Data parsing error",{socket_id:e.id,error_message:s.message}),d(e,{message:"Invalid data format"})}}handle_socket_end(e){e.id&&(this.connections.delete(e.id),this.authenticated_clients.delete(e.id),this.connection_count--,this.update_connection_count()),this.log.info("Client disconnected",{socket_id:e.id})}check_op_type(e=""){return e?p.includes(e):!1}route_operation(e,t,s){switch(t){case"authentication":this.handle_authentication(e,s);break;case"setup":this.handle_setup(e,s);break;case"find_one":case"find":case"get_indexes":this.handle_read_operation(e,t,s);break;case"create_index":case"drop_index":this.handle_write_operation(e,t,s);break;case"insert_one":case"update_one":case"delete_one":case"bulk_write":this.handle_write_operation(e,t,s);break;case"ping":v(e);break;case"admin":b(e,s,this.is_authenticated.bind(this));break;default:d(e,{message:`Unsupported operation: ${t}`})}}handle_authentication(e,t){if(this.is_authenticated(e))l(e,"Already authenticated");else{const s=`${e.id}_${Date.now()}`;process.send({type:"auth_request",data:{auth_id:s,socket_id:e.id,password:t.password}}),this.pending_writes.set(s,{socket:e,type:"auth"})}}handle_setup(e,t){const s=`${e.id}_${Date.now()}`;process.send({type:"setup_request",data:{setup_id:s,socket_id:e.id}}),this.pending_writes.set(s,{socket:e,type:"setup"})}handle_read_operation(e,t,s){if(!this.is_authenticated(e)){d(e,{message:"Authentication required"});return}const r=`${e.id}_${++this.write_id_counter}`;process.send({type:"write_request",data:{write_id:r,op_type:t,data:s,socket_id:e.id}}),this.pending_writes.set(r,{socket:e,type:"read"})}handle_write_operation(e,t,s){if(!this.is_authenticated(e)){d(e,{message:"Authentication required"});return}const r=`${e.id}_${++this.write_id_counter}`;process.send({type:"write_request",data:{write_id:r,op_type:t,data:s,socket_id:e.id}}),this.pending_writes.set(r,{socket:e,type:"write"})}handle_write_response(e){const{write_id:t,success:s,result:r,error:a}=e.data,i=this.pending_writes.get(t);if(!i){this.log.warn("No pending write found",{write_id:t});return}const{socket:o}=i;if(this.pending_writes.delete(t),o.destroyed||!o.writable){this.log.warn("Socket disconnected before response could be sent",{write_id:t});return}try{if(s){let n;Array.isArray(r)?n={ok:1,documents:r}:r&&typeof r=="object"?n={ok:1,...r}:n={ok:1,result:r};const _=c(n);o.write(_)}else{const _=c({ok:0,error:a});o.write(_)}}catch(n){this.log.error("Error sending response to client",{write_id:t,error:n.message})}}handle_auth_response(e){const{auth_id:t,success:s,message:r}=e.data,a=this.pending_writes.get(t);if(!a){this.log.warn("No pending auth found",{auth_id:t});return}const{socket:i}=a;if(this.pending_writes.delete(t),i.destroyed||!i.writable){this.log.warn("Socket disconnected before auth response could be sent",{auth_id:t});return}try{if(s){this.authenticated_clients.add(i.id);const n=c({ok:1,version:"1.0.0",message:r});i.write(n)}else d(i,{message:r}),i.end()}catch(o){this.log.error("Error sending auth response to client",{auth_id:t,error:o.message})}}handle_setup_response(e){const{setup_id:t,success:s,password:r,message:a,error:i}=e.data,o=this.pending_writes.get(t);if(!o){this.log.warn("No pending setup found",{setup_id:t});return}const{socket:n}=o;this.pending_writes.delete(t),s?u(n,{password:r,message:a}):d(n,{message:i})}handle_write_notification(e){this.log.info("Received write notification",{op_type:e.data.op_type,timestamp:e.data.timestamp})}is_authenticated(e){return this.authenticated_clients.has(e.id)}update_connection_count(){process.connected&&process.send({type:"connection_count",data:{count:this.connection_count}})}send_heartbeat(){if(process.connected)try{process.send({type:"heartbeat",data:{timestamp:Date.now()}})}catch{clearInterval(this.heartbeat_interval)}}async shutdown(){const e=Date.now();this.log.info("Initiating graceful shutdown");try{await g(),this.log.info("Write queue shutdown complete")}catch(s){this.log.error("Error shutting down write queue",{error:s.message})}try{await y(),this.log.info("Database cleanup complete")}catch(s){this.log.error("Error cleaning up database",{error:s.message})}this.server&&this.server.close(()=>{this.log.info("Server closed")});for(const[s,r]of this.connections)r.end();const t=process.env.NODE_ENV==="test"?100:5e3;setTimeout(()=>{const s=Date.now()-e;this.log.info("Worker shutdown complete",{shutdown_duration_ms:s}),process.exit(0)},t)}}const M=new k;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import k from"net";import{decode as C}from"msgpackr";import E from"./lib/op_types.js";import b from"./lib/safe_json_parse.js";import{load_settings as y,get_settings as g,get_port_configuration as S}from"./lib/load_settings.js";import{send_error as f}from"./lib/send_response.js";import{start_cluster as q}from"./cluster/index.js";import x from"./lib/logger.js";import{initialize_database as N,cleanup_database as A}from"./lib/query_engine.js";import{create_message_parser as B,encode_message as _}from"./lib/tcp_protocol.js";import{create_connection_manager as D}from"./lib/connection_manager.js";import{shutdown_write_queue as $}from"./lib/write_queue.js";import{setup_authentication as F,verify_password as J,get_client_ip as K,is_rate_limited as P,initialize_auth_manager as j,reset_auth_state as G}from"./lib/auth_manager.js";import{initialize_api_key_manager as M}from"./lib/api_key_manager.js";import{restore_backup as W,start_backup_schedule as H,stop_backup_schedule as U}from"./lib/backup_manager.js";import{initialize_replication_manager as V,shutdown_replication_manager as Y}from"./lib/replication_manager.js";import{initialize_write_forwarder as L,shutdown_write_forwarder as Q}from"./lib/write_forwarder.js";import{handle_database_operation as X,handle_admin_operation as Z,handle_ping_operation as ee}from"./lib/operation_dispatcher.js";import{start_http_server as re,stop_http_server as te}from"./lib/http_server.js";import{create_recovery_token as oe,initialize_recovery_manager as T,reset_recovery_state as ne}from"./lib/recovery_manager.js";const d=new Set;let i=null;const se=async(t,r={})=>{if(!r?.password){const s=_({ok:0,error:"Authentication operation requires password to be set in data."});t.write(s),t.end();return}try{const o=K(t);if(P(o)){const n=_({ok:0,error:"Too many failed attempts. Please try again later."});t.write(n),t.end();return}if(!await J(r.password,o)){const n=_({ok:0,error:"Authentication failed"});t.write(n),t.end();return}d.add(t.id);const e=_({ok:1,version:"1.0.0",message:"Authentication successful"});t.write(e)}catch(o){const s={ok:0,error:`Authentication error: ${o.message}`},a=_(s);t.write(a),t.end()}},ae=async(t,r={})=>{try{const s={ok:1,password:F(),message:"Authentication setup completed successfully. Save this password - it will not be shown again."},a=_(s);t.write(a)}catch(o){const s={ok:0,error:`Setup error: ${o.message}`},a=_(s);t.write(a)}},ie=(t="")=>{if(!t)throw new Error("Must pass an op type for operation.");return E.includes(t)},Ee=t=>{try{if(typeof t=="string")return b(t);if(Buffer.isBuffer(t)){const r=C(t);return typeof r=="string"?b(r):r}else return t}catch{return null}},v=t=>d.has(t.id),qe=async()=>{const{create_context_logger:t}=x("server"),r=t();let o=null;try{y(),o=g()}catch{}if(o?.restore_from)try{r.info("Startup restore requested",{backup_filename:o.restore_from});const e=await W(o.restore_from);r.info("Startup restore completed",{backup_filename:o.restore_from,duration_ms:e.duration_ms});const c={...o};delete c.restore_from,process.env.JOYSTICK_DB_SETTINGS=JSON.stringify(c),y(),o=g(),r.info("Removed restore_from from settings after successful restore")}catch(e){r.error("Startup restore failed",{backup_filename:o.restore_from,error:e.message}),r.info("Continuing with fresh database after restore failure")}N(),j(),M(),T();try{V(),r.info("Replication manager initialized")}catch(e){r.warn("Failed to initialize replication manager",{error:e.message})}try{L(),r.info("Write forwarder initialized")}catch(e){r.warn("Failed to initialize write forwarder",{error:e.message})}if(o?.s3)try{H(),r.info("Backup scheduling started")}catch(e){r.warn("Failed to start backup scheduling",{error:e.message})}i=D({max_connections:1e3,idle_timeout:600*1e3,request_timeout:5*1e3});let s=null;try{const{http_port:e}=S();s=await re(e),s&&r.info("HTTP server started",{http_port:e})}catch(e){r.warn("Failed to start HTTP server",{error:e.message})}const a=k.createServer((e={})=>{if(!i.add_connection(e))return;const c=B();e.on("data",async n=>{i.update_activity(e.id);try{const h=c.parse_messages(n);for(const O of h){const u=O,l=u?.op||null;if(!l){f(e,{message:"Missing operation type"});continue}if(!ie(l)){f(e,{message:"Invalid operation type"});continue}const z=i.create_request_timeout(e.id,l);try{switch(l){case"authentication":await se(e,u?.data||{});break;case"setup":await ae(e,u?.data||{});break;case"insert_one":case"update_one":case"delete_one":case"bulk_write":case"find_one":case"find":case"create_index":case"drop_index":case"get_indexes":await X(e,l,u?.data||{},v,n.length,i,d);break;case"ping":ee(e);break;case"admin":await Z(e,u?.data||{},v,i,d);break;case"reload":if(!v(e)){f(e,{message:"Authentication required"});break}try{let p=null;try{p=g()}catch{}let m=null;try{await y(),m=g()}catch{m={port:1983,authentication:{}}}const w={ok:1,status:"success",message:"Configuration reloaded successfully",changes:{port_changed:p?p.port!==m.port:!1,authentication_changed:p?p.authentication?.password_hash!==m.authentication?.password_hash:!1},timestamp:new Date().toISOString()},I=_(w);e.write(I)}catch(p){const m={ok:0,error:`Reload operation failed: ${p.message}`},w=_(m);e.write(w)}break;default:f(e,{message:`Operation ${l} not implemented`})}}finally{clearTimeout(z)}}}catch(h){r.error("Message parsing failed",{client_id:e.id,error:h.message}),f(e,{message:"Invalid message format"}),e.end()}}),e.on("end",()=>{r.info("Client disconnected",{socket_id:e.id}),d.delete(e.id),i.remove_connection(e.id)}),e.on("error",n=>{r.error("Socket error",{socket_id:e.id,error:n.message}),d.delete(e.id),i.remove_connection(e.id)})});return a.cleanup=async()=>{try{await te(),U(),await Y(),await Q(),i&&i.shutdown(),d.clear(),await $(),await new Promise(e=>setTimeout(e,100)),await A(),G(),ne()}catch{}},a};if(import.meta.url===`file://${process.argv[1]}`){const{create_context_logger:t}=x("main"),r=t();if(process.argv.includes("--generate-recovery-token"))try{T();const n=oe();console.log("Emergency Recovery Token Generated"),console.log(`Visit: ${n.url}`),console.log("Token expires in 10 minutes"),r.info("Recovery token generated via CLI",{expires_at:new Date(n.expires_at).toISOString()}),process.exit(0)}catch(n){console.error("Failed to generate recovery token:",n.message),r.error("Recovery token generation failed",{error:n.message}),process.exit(1)}const{tcp_port:o,http_port:s}=S(),a={worker_count:process.env.WORKER_COUNT?parseInt(process.env.WORKER_COUNT):void 0,port:o,environment:process.env.NODE_ENV||"development"},{has_settings:e}=await import("./lib/load_settings.js"),c=e();r.info("Starting JoystickDB server...",{workers:a.worker_count||"auto",tcp_port:o,http_port:s,environment:a.environment,has_settings:c,port_source:c?"JOYSTICK_DB_SETTINGS":"default"}),q(a)}export{se as authentication,ie as check_op_type,qe as create_server,Ee as parse_data,ae as setup};
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import{readFileSync as b,writeFileSync as O,existsSync as m,unlinkSync as P}from"fs";import A from"crypto";import y from"bcrypt";import E from"./logger.js";import{get_database as c,build_collection_key as g}from"./query_engine.js";const{create_context_logger:x}=E("api_key_manager"),l=x(),d="./API_KEY",i="_users",h=12;let u=null,_=!1;const T=()=>{const e="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";let r="";const s=A.randomBytes(32);for(let o=0;o<32;o++)r+=e[s[o]%e.length];return r},w=()=>{if(u)return u;if(m(d))try{return u=b(d,"utf8").trim(),l.info("API key loaded from file"),u}catch(r){throw l.error("Failed to read API key file",{error:r.message}),new Error(`Failed to read API key file: ${r.message}`)}const e=T();try{return O(d,e,{mode:384}),u=e,l.info("New API key generated and saved",{file_path:d}),N(e),e}catch(r){throw l.error("Failed to save API key file",{error:r.message}),new Error(`Failed to save API key file: ${r.message}`)}},N=e=>{const r=process.env.JOYSTICK_DB_PORT||"1983",s=process.env.JOYSTICK_DB_HTTP_PORT||"1984";console.log(`
|
|
2
|
+
JoystickDB Setup
|
|
3
|
+
`),console.log(`To finish setting up your database and enable operations for authenticated users, you will need to create an admin user via the database's admin API using the API key displayed below.
|
|
4
|
+
`),console.log("=== STORE THIS KEY SECURELY -- IT WILL NOT BE DISPLAYED AGAIN ==="),console.log(e),console.log(`===
|
|
5
|
+
`),console.log(`To create a user, send a POST request to the server:
|
|
6
|
+
`),console.log(`fetch('http://localhost:${s}/api/users', {`),console.log(" method: 'POST',"),console.log(" headers: {"),console.log(" 'Content-Type': 'application/json',"),console.log(` 'x-joystick-db-api-key': '${e}'`),console.log(" },"),console.log(" body: JSON.stringify({"),console.log(" username: 'admin',"),console.log(" password: 'your_secure_password',"),console.log(" role: 'read_write'"),console.log(" })"),console.log(`});
|
|
7
|
+
`),console.log("==!=="),console.log("Store this key securely. It will not be displayed again."),console.log(`==!==
|
|
8
|
+
`),console.log(`API key saved to: ${d}
|
|
9
|
+
`)},F=e=>{if(!e)return!1;const r=w();return e===r},J=e=>!e||typeof e!="string"?!1:/^[a-zA-Z0-9]{3,50}$/.test(e),S=e=>!e||typeof e!="string"?{valid:!1,error:"Password is required"}:e.length<8?{valid:!1,error:"Password must be at least 8 characters long"}:e.length>128?{valid:!1,error:"Password must be no more than 128 characters long"}:{valid:!0},k=e=>["read","write","read_write"].includes(e),R=async e=>{const{username:r,password:s,role:o}=e;if(!J(r))throw new Error("Username must be alphanumeric and 3-50 characters long");const t=S(s);if(!t.valid)throw new Error(t.error);if(!k(o))throw new Error("Role must be one of: read, write, read_write");const a=c(),p=await y.hash(s,h),n={username:r,password_hash:p,role:o,created_at:new Date().toISOString(),updated_at:new Date().toISOString()},f=g(i,r);let v=null;return await a.transaction(()=>{if(a.get(f))throw new Error("Username already exists");a.put(f,JSON.stringify(n)),v=n}),o==="read_write"&&(_=!0,l.info("Admin user created, API key requirement relaxed for authenticated operations")),l.info("User created successfully",{username:r,role:o}),{username:n.username,role:n.role,created_at:n.created_at}},U=()=>{const e=c(),r=[];for(const{key:s,value:o}of e.getRange({start:`${i}:`,end:`${i}:\xFF`})){const t=JSON.parse(o),a={username:t.username,role:t.role,created_at:t.created_at,updated_at:t.updated_at};r.push(a)}return r},D=e=>{const r=c(),s=g(i,e),o=r.get(s);if(!o)return null;const t=JSON.parse(o);return{username:t.username,role:t.role,created_at:t.created_at,updated_at:t.updated_at}},$=async(e,r)=>{const s=c(),o=g(i,e);let t=null;if(r.password){const f=S(r.password);if(!f.valid)throw new Error(f.error);t=await y.hash(r.password,h)}if(r.role&&!k(r.role))throw new Error("Role must be one of: read, write, read_write");const a=s.get(o);if(!a)throw new Error("User not found");const n={...JSON.parse(a)};return r.role&&(n.role=r.role),t&&(n.password_hash=t),n.updated_at=new Date().toISOString(),s.putSync(o,JSON.stringify(n)),l.info("User updated successfully",{username:e,updates:Object.keys(r)}),{username:n.username,role:n.role,created_at:n.created_at,updated_at:n.updated_at}},L=e=>{const r=c(),s=g(i,e);if(!r.get(s))throw new Error("User not found");const t=r.removeSync(s);return l.info("User deleted successfully",{username:e}),!0},Y=async(e,r)=>{const s=c(),o=g(i,e),t=s.get(o);if(!t)return null;const a=JSON.parse(t);return await y.compare(r,a.password_hash)?{username:a.username,role:a.role,created_at:a.created_at,updated_at:a.updated_at}:null},I=()=>{if(_)return!0;const e=c();for(const{value:r}of e.getRange({start:`${i}:`,end:`${i}:\xFF`}))if(JSON.parse(r).role==="read_write")return _=!0,!0;return!1},C=()=>{w(),_=I(),_&&l.info("Admin user detected, API key requirement relaxed for authenticated operations")},B=()=>{if(u=null,_=!1,m(d))try{P(d)}catch{}};export{I as check_admin_user_exists,R as create_user,L as delete_user,U as get_all_users,D as get_user,C as initialize_api_key_manager,w as load_or_generate_api_key,B as reset_api_key_state,$ as update_user,F as validate_api_key,Y as verify_user_password};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"fs";import d from"bcrypt";import S from"crypto";import y from"./logger.js";const{create_context_logger:v}=y("auth_manager"),n=v();import{load_settings as T,has_settings as E}from"./load_settings.js";const I=12,h=60*1e3,f=5,b=1e3,O=2;let e=null,i=new Map,c=new Map;const D=()=>S.randomBytes(16).toString("hex"),u=()=>{try{if(!E())return null;const t=T();return e=t,t.authentication&&t.authentication.failed_attempts&&(i=new Map(Object.entries(t.authentication.failed_attempts))),t.authentication&&t.authentication.rate_limits&&(c=new Map(Object.entries(t.authentication.rate_limits))),n.info("Settings data loaded successfully from environment variable"),e}catch(t){throw n.error("Failed to load settings data",{error:t.message}),new Error(`Failed to load settings data: ${t.message}`)}},_=()=>{try{if(!e)throw new Error("No settings data to save");e.authentication||(e.authentication={}),e.authentication.failed_attempts=Object.fromEntries(i),e.authentication.rate_limits=Object.fromEntries(c),process.env.JOYSTICK_DB_SETTINGS=JSON.stringify(e),n.info("Settings data saved successfully to environment variable")}catch(t){throw n.error("Failed to save settings data",{error:t.message}),new Error(`Failed to save settings data: ${t.message}`)}},m=t=>process.env.NODE_ENV==="test"?!1:["127.0.0.1","::1","localhost"].includes(t),M=t=>t.remoteAddress||"127.0.0.1",g=t=>{if(m(t))return!1;const a=Date.now(),r=(i.get(t)||[]).filter(o=>a-o<h);if(r.length>=f){const o=c.get(t);if(o&&a<o.expires_at)return!0;const l=Math.min(b*Math.pow(O,Math.floor(r.length/f)),1800*1e3);return c.set(t,{expires_at:a+l,attempts:r.length}),_(),n.warn("IP rate limited",{ip:t,attempts:r.length,backoff_duration_ms:l}),!0}return!1},p=t=>{if(m(t))return;const a=Date.now(),s=i.get(t)||[];s.push(a);const r=s.filter(o=>a-o<h);i.set(t,r),n.warn("Failed authentication attempt recorded",{ip:t,total_recent_attempts:r.length}),_()},A=t=>{i.delete(t),c.delete(t),_()},F=()=>{if(e||u(),e||(e={port:1983,cluster:!0,worker_count:2,authentication:{},backup:{enabled:!1},replication:{enabled:!1,role:"primary"},auto_indexing:{enabled:!0,threshold:100},performance:{monitoring_enabled:!0,log_slow_queries:!0,slow_query_threshold_ms:1e3},logging:{level:"info",structured:!0}}),e.authentication&&e.authentication.password_hash)throw new Error("Authentication already configured. Update JOYSTICK_DB_SETTINGS environment variable to reconfigure.");const t=D(),a=d.hashSync(t,I),s=new Date().toISOString();return e.authentication={password_hash:a,created_at:s,last_updated:s,failed_attempts:{},rate_limits:{}},_(),n.info("Authentication setup completed",{created_at:s}),t},N=async(t,a)=>{if(!e)try{u()}catch{}if(!e||!e.authentication||!e.authentication.password_hash)throw new Error("Authentication not configured. Run setup first.");if(g(a))throw new Error("Too many failed attempts. Please try again later.");const s=Date.now();try{const r=await d.compare(t,e.authentication.password_hash),o=Date.now()-s,l=100;return o<l&&await new Promise(w=>setTimeout(w,l-o)),r?(A(a),n.info("Password verification successful",{ip:a}),!0):(p(a),n.warn("Password verification failed",{ip:a}),!1)}catch(r){throw p(a),n.error("Password verification error",{ip:a,error:r.message}),r}},B=()=>({configured:!!(e&&e.authentication&&e.authentication.password_hash),failed_attempts_count:i.size,rate_limited_ips:c.size,created_at:e?.authentication?.created_at||null,last_updated:e?.authentication?.last_updated||null}),P=()=>{try{u()}catch(t){n.warn("Could not load settings data on startup",{error:t.message})}},k=()=>{e=null,i=new Map,c=new Map,process.env.JOYSTICK_DB_SETTINGS&&delete process.env.JOYSTICK_DB_SETTINGS};export{B as get_auth_stats,M as get_client_ip,P as initialize_auth_manager,g as is_rate_limited,k as reset_auth_state,F as setup_authentication,N as verify_password};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{get_database as A}from"./query_engine.js";import{create_index as k,get_indexes as z,drop_index as w}from"./index_manager.js";import{get_settings as Q}from"./load_settings.js";import R from"./logger.js";const{create_context_logger:_}=R("auto_index_manager");let l=null,u=new Map,i=new Map,x=null;const E=()=>(l||(l=A().openDB("auto_indexes",{create:!0}),H(),G(),D()),l),h=()=>{if(!l)throw new Error("Auto index database not initialized. Call initialize_auto_index_database first.");return l},g=()=>{try{return Q().auto_indexing||{enabled:!0,frequency_threshold:100,performance_threshold_ms:50,max_auto_indexes_per_collection:10,monitoring_window_hours:24,cleanup_unused_after_hours:168,excluded_fields:["_id","created_at"],included_collections:["*"],excluded_collections:[]}}catch{return{enabled:!0,frequency_threshold:100,performance_threshold_ms:50,max_auto_indexes_per_collection:10,monitoring_window_hours:24,cleanup_unused_after_hours:168,excluded_fields:["_id","created_at"],included_collections:["*"],excluded_collections:[]}}},B=e=>{const o=g();return o.excluded_collections.includes(e)?!1:o.included_collections.includes("*")?!0:o.included_collections.includes(e)},C=e=>!g().excluded_fields.includes(e),N=e=>{const o=[];if(!e||typeof e!="object")return o;for(const[t,a]of Object.entries(e))C(t)&&o.push(t);return o},P=(e,o,t,a=!1,s=null)=>{const r=_(),n=g();if(!n.enabled||!B(e)||!l)return;const c=N(o),f=new Date;u.has(e)||u.set(e,new Map);const d=u.get(e);for(const y of c){d.has(y)||d.set(y,{query_count:0,total_time_ms:0,avg_time_ms:0,last_queried:f,slow_query_count:0,used_index_count:0});const m=d.get(y);m.query_count++,m.total_time_ms+=t,m.avg_time_ms=m.total_time_ms/m.query_count,m.last_queried=f,t>n.performance_threshold_ms&&m.slow_query_count++,a&&(s===y||s===null)&&m.used_index_count++}r.debug("Query recorded for auto-indexing analysis",{collection:e,fields:c,execution_time_ms:t,used_index:a,indexed_field:s})},T=()=>{const e=_();try{const o=h(),t={};for(const[a,s]of u.entries()){t[a]={};for(const[r,n]of s.entries())t[a][r]={...n,last_queried:n.last_queried.toISOString()}}o.put("query_stats",t),e.debug("Query statistics saved to database")}catch(o){e.error("Failed to save query statistics",{error:o.message})}},G=()=>{const e=_();try{const t=h().get("query_stats");if(t){u.clear();for(const[a,s]of Object.entries(t)){const r=new Map;for(const[n,c]of Object.entries(s))r.set(n,{...c,last_queried:new Date(c.last_queried)});u.set(a,r)}e.debug("Query statistics loaded from database")}}catch(o){e.error("Failed to load query statistics",{error:o.message})}},p=()=>{const e=_();try{const o=h(),t={};for(const[a,s]of i.entries()){t[a]={};for(const[r,n]of s.entries())t[a][r]={...n,created_at:n.created_at.toISOString(),last_used:n.last_used?n.last_used.toISOString():null}}o.put("auto_index_metadata",t),e.debug("Auto index metadata saved to database")}catch(o){e.error("Failed to save auto index metadata",{error:o.message})}},H=()=>{const e=_();try{const t=h().get("auto_index_metadata");if(t){i.clear();for(const[a,s]of Object.entries(t)){const r=new Map;for(const[n,c]of Object.entries(s))r.set(n,{...c,created_at:new Date(c.created_at),last_used:c.last_used?new Date(c.last_used):null});i.set(a,r)}e.debug("Auto index metadata loaded from database")}}catch(o){e.error("Failed to load auto index metadata",{error:o.message})}},q=(e,o)=>{try{const t=i.get(e);return!!(t&&t.has(o))}catch{return!1}},v=()=>{const e=g(),o=[],t=new Date,a=e.monitoring_window_hours*60*60*1e3;for(const[s,r]of u.entries()){const n=z("default",s);if(!(n.filter(f=>q(s,f.field)).length>=e.max_auto_indexes_per_collection))for(const[f,d]of r.entries()){if(t-d.last_queried>a||n.some(S=>S.field===f))continue;const j=d.query_count>=e.frequency_threshold,I=d.avg_time_ms>=e.performance_threshold_ms,M=d.slow_query_count>0;(j||I&&M)&&o.push({collection:s,field:f,stats:{...d},priority:d.slow_query_count*2+d.query_count/e.frequency_threshold})}}return o.sort((s,r)=>r.priority-s.priority)},b=async(e,o,t)=>{const a=_();try{return await k("default",e,o,{sparse:!0}),i.has(e)||i.set(e,new Map),i.get(e).set(o,{created_at:new Date,query_count_at_creation:t.query_count,avg_performance_improvement_ms:0,last_used:null,usage_count:0,auto_created:!0}),p(),a.info("Automatic index created",{collection:e,field:o,query_count:t.query_count,avg_time_ms:t.avg_time_ms,slow_query_count:t.slow_query_count}),!0}catch(s){return a.error("Failed to create automatic index",{collection:e,field:o,error:s.message}),!1}},F=async()=>{const e=_();if(g().enabled)try{const t=v();if(t.length===0){e.debug("No automatic index candidates found");return}e.info("Evaluating automatic index candidates",{candidate_count:t.length});for(const a of t.slice(0,5))await b(a.collection,a.field,a.stats)&&await new Promise(r=>setTimeout(r,100))}catch(t){e.error("Failed to evaluate automatic indexes",{error:t.message})}},J=async()=>{const e=_(),o=g(),t=new Date,a=o.cleanup_unused_after_hours*60*60*1e3;try{for(const[s,r]of i.entries())for(const[n,c]of r.entries())c.last_used?t-c.last_used>a&&(await w("default",s,n),r.delete(n),e.info("Removed unused automatic index",{collection:s,field:n,last_used:c.last_used,usage_count:c.usage_count})):t-c.created_at>a&&(await w("default",s,n),r.delete(n),e.info("Removed unused automatic index",{collection:s,field:n,created_at:c.created_at,usage_count:c.usage_count}));p()}catch(s){e.error("Failed to cleanup unused indexes",{error:s.message})}},K=(e,o)=>{const t=i.get(e);if(t&&t.has(o)){const a=t.get(o);a.last_used=new Date,a.usage_count++}},D=()=>{const e=g();x&&clearInterval(x),e.enabled&&(x=setInterval(async()=>{T(),await F(),await J()},6e4))},O=()=>{x&&(clearInterval(x),x=null)},L=(e=null)=>{if(e){const t=u.get(e);if(!t)return{};const a={};for(const[s,r]of t.entries())a[s]={...r};return a}const o={};for(const[t,a]of u.entries()){o[t]={};for(const[s,r]of a.entries())o[t][s]={...r}}return o},U=()=>{const e={total_auto_indexes:0,collections:{}};for(const[o,t]of i.entries()){e.collections[o]={};for(const[a,s]of t.entries())e.total_auto_indexes++,e.collections[o][a]={...s}}return e},V=async(e=null)=>{const o=_();try{if(e){const t=v().filter(a=>a.collection===e);for(const a of t)await b(a.collection,a.field,a.stats);o.info("Forced index evaluation completed",{collection:e,candidates_processed:t.length})}else await F(),o.info("Forced index evaluation completed for all collections");return{acknowledged:!0}}catch(t){throw o.error("Failed to force index evaluation",{error:t.message}),t}},W=async(e,o=null)=>{const t=_();try{const a=i.get(e);if(!a)return{acknowledged:!0,removed_count:0};const s=o||Array.from(a.keys());let r=0;for(const n of s)a.has(n)&&(await w("default",e,n),a.delete(n),r++,t.info("Removed automatic index",{collection:e,field:n}));return p(),{acknowledged:!0,removed_count:r}}catch(a){throw t.error("Failed to remove automatic indexes",{collection:e,field_names:o,error:a.message}),a}},X=()=>{if(O(),l)try{l.remove("query_stats"),l.remove("auto_index_metadata")}catch{}u.clear(),i.clear(),l=null};export{X as cleanup_auto_index_database,V as force_index_evaluation,h as get_auto_index_database,U as get_auto_index_statistics,L as get_query_statistics,E as initialize_auto_index_database,q as is_auto_created_index,K as record_index_usage,P as record_query,W as remove_automatic_indexes,D as start_evaluation_timer,O as stop_evaluation_timer};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import{S3Client as F,PutObjectCommand as L,GetObjectCommand as W,ListObjectsV2Command as x,DeleteObjectCommand as j}from"@aws-sdk/client-s3";import{createReadStream as M,createWriteStream as G,existsSync as w,unlinkSync as $,statSync as T}from"fs";import{mkdir as v,rm as b}from"fs/promises";import{resolve as z,join as C}from"path";import{spawn as B}from"child_process";import{createHash as q}from"crypto";import{get_settings as I}from"./load_settings.js";import{get_database as A}from"./query_engine.js";import H from"./logger.js";const{create_context_logger:f}=H("backup_manager");let D=null,y=null;const S=()=>{const e=f();try{const t=I();if(!t.s3)throw new Error("S3 configuration not found in settings");const{bucket:c,region:r,access_key:o,secret_key:d,endpoint:n}=t.s3;if(!c||!r||!o||!d)throw new Error("Missing required S3 configuration: bucket, region, access_key, secret_key");if(!D){const s={region:r,credentials:{accessKeyId:o,secretAccessKey:d}};n&&n!==`https://s3.${r}.amazonaws.com`&&(s.endpoint=n,s.forcePathStyle=!0),D=new F(s),e.info("S3 client initialized",{bucket:c,region:r,endpoint:n||"default"})}return{client:D,bucket:c}}catch(t){throw e.error("Failed to initialize S3 client",{error:t.message}),t}},R=async()=>{const e=f();try{const{client:t,bucket:c}=S(),r=new x({Bucket:c,MaxKeys:1});return await t.send(r),e.info("S3 connection test successful",{bucket:c}),!0}catch(t){throw e.error("S3 connection test failed",{error:t.message}),new Error(`S3 connection failed: ${t.message}`)}},K=e=>new Promise((t,c)=>{const r=q("sha256"),o=M(e);o.on("data",d=>r.update(d)),o.on("end",()=>t(r.digest("hex"))),o.on("error",c)}),E=async()=>{const e=f(),t=Date.now();try{const{client:c,bucket:r}=S(),o=z("./temp_backup");await v(o,{recursive:!0});const n=`joystickdb-backup-${new Date().toISOString().replace(/[:.]/g,"-")}.tar.gz`,s=C(o,n);e.info("Starting backup creation",{backup_filename:n,temp_backup_path:s});const u=B("tar",["--sparse","-czf",s,"-C","./data","."],{stdio:["pipe","pipe","pipe"]});let _="",l="";u.stdout.on("data",k=>{_+=k.toString()}),u.stderr.on("data",k=>{l+=k.toString()});const p=await new Promise(k=>{u.on("close",k)});if(p!==0)throw new Error(`Tar process failed with exit code ${p}: ${l}`);const a=await K(s),i=T(s);e.info("Backup file created",{backup_filename:n,size_bytes:i.size,size_mb:Math.round(i.size/1024/1024),checksum:a});const m=M(s),g=new L({Bucket:r,Key:n,Body:m,Metadata:{checksum:a,created_at:new Date().toISOString(),size_bytes:i.size.toString()}});await c.send(g),$(s),await b(o,{recursive:!0,force:!0});const h=Date.now()-t;return e.info("Backup completed successfully",{backup_filename:n,duration_ms:h,size_mb:Math.round(i.size/1024/1024),checksum:a,s3_bucket:r}),{filename:n,size_bytes:i.size,checksum:a,created_at:new Date().toISOString(),duration_ms:h,s3_location:`s3://${r}/${n}`}}catch(c){e.error("Backup creation failed",{error:c.message});try{const r=z("./temp_backup");w(r)&&await b(r,{recursive:!0,force:!0})}catch(r){e.warn("Failed to clean up temporary backup files",{error:r.message})}throw c}},O=async()=>{const e=f();try{const{client:t,bucket:c}=S(),r=new x({Bucket:c,Prefix:"joystickdb-backup-"}),o=await t.send(r);if(!o.Contents||o.Contents.length===0)return{backups:[],total_count:0,total_size_bytes:0};const d=o.Contents.filter(s=>s.Key.startsWith("joystickdb-backup-")&&s.Key.endsWith(".tar.gz")).map(s=>({filename:s.Key,size_bytes:s.Size,size_mb:Math.round(s.Size/1024/1024),last_modified:s.LastModified.toISOString(),s3_location:`s3://${c}/${s.Key}`})).sort((s,u)=>new Date(u.last_modified)-new Date(s.last_modified)),n=d.reduce((s,u)=>s+u.size_bytes,0);return e.info("Listed backups",{total_count:d.length,total_size_mb:Math.round(n/1024/1024)}),{backups:d,total_count:d.length,total_size_bytes:n,total_size_mb:Math.round(n/1024/1024)}}catch(t){throw e.error("Failed to list backups",{error:t.message}),t}},V=async e=>{const t=f(),c=Date.now();try{const{client:r,bucket:o}=S(),d=z("./temp_restore");await v(d,{recursive:!0});const n=C(d,e);t.info("Starting backup restore",{backup_filename:e,temp_backup_path:n});const s=new W({Bucket:o,Key:e}),u=await r.send(s);if(!u.Body)throw new Error("Empty backup file received from S3");const _=G(n);if(await new Promise((a,i)=>{u.Body.pipe(_),_.on("finish",a),_.on("error",i),u.Body.on("error",i)}),u.Metadata&&u.Metadata.checksum){const a=await K(n);if(a!==u.Metadata.checksum)throw new Error(`Backup checksum mismatch. Expected: ${u.Metadata.checksum}, Got: ${a}`);t.info("Backup checksum verified",{checksum:a})}const l=A(),p=z("./data_backup_before_restore");if(w("./data")){const a=B("cp",["-r","./data",p]);await new Promise(i=>{a.on("close",i)})}try{w("./data")&&await b("./data",{recursive:!0,force:!0}),await v("./data",{recursive:!0});const a=B("tar",["-xzf",n,"-C","./data"],{stdio:["pipe","pipe","pipe"]});let i="";a.stderr.on("data",h=>{i+=h.toString()});const m=await new Promise(h=>{a.on("close",h)});if(m!==0)throw new Error(`Tar extraction failed with exit code ${m}: ${i}`);$(n),await b(d,{recursive:!0,force:!0}),w(p)&&await b(p,{recursive:!0,force:!0});const g=Date.now()-c;return t.info("Backup restore completed successfully",{backup_filename:e,duration_ms:g}),{backup_filename:e,restored_at:new Date().toISOString(),duration_ms:g,status:"success"}}catch(a){if(w(p)){w("./data")&&await b("./data",{recursive:!0,force:!0});const i=B("mv",[p,"./data"]);await new Promise(m=>{i.on("close",m)})}throw a}}catch(r){t.error("Backup restore failed",{backup_filename:e,error:r.message});try{const o=z("./temp_restore");w(o)&&await b(o,{recursive:!0,force:!0})}catch(o){t.warn("Failed to clean up temporary restore files",{error:o.message})}throw r}},P=async()=>{const e=f();try{const{client:t,bucket:c}=S(),o=(await O()).backups;if(o.length===0)return{deleted_count:0,retained_count:0};o.sort((a,i)=>new Date(i.last_modified)-new Date(a.last_modified));const d=new Date,n=3600*1e3,s=24*n,u=[],_=[],l=[];for(const a of o){const i=new Date(a.last_modified),m=d-i;if(m<=24*n)u.push(a);else if(m<=30*s){const g=i.toDateString();_.find(k=>new Date(k.last_modified).toDateString()===g)?l.push(a):_.push(a)}else l.push(a)}u.length>24&&l.push(...u.slice(24));for(const a of l)try{const i=new j({Bucket:c,Key:a.filename});await t.send(i),e.info("Deleted old backup",{filename:a.filename})}catch(i){e.warn("Failed to delete backup",{filename:a.filename,error:i.message})}const p=o.length-l.length;return e.info("Backup cleanup completed",{deleted_count:l.length,retained_count:p,hourly_backups:Math.min(u.length,24),daily_backups:_.length}),{deleted_count:l.length,retained_count:p,hourly_backups:Math.min(u.length,24),daily_backups:_.length}}catch(t){throw e.error("Backup cleanup failed",{error:t.message}),t}},J=()=>{const e=f();try{const t=I();if(!t.s3){e.info("S3 not configured, backup scheduling disabled");return}const c=t.backup_schedule||"hourly";let r;switch(c){case"hourly":r=3600*1e3;break;case"daily":r=1440*60*1e3;break;case"weekly":r=10080*60*1e3;break;default:e.warn("Invalid backup schedule, using hourly",{schedule:c}),r=3600*1e3}y&&clearInterval(y),y=setInterval(async()=>{try{e.info("Starting scheduled backup",{schedule:c}),await E(),await P()}catch(o){e.error("Scheduled backup failed",{error:o.message})}},r),e.info("Backup schedule started",{schedule:c,interval_ms:r})}catch(t){e.error("Failed to start backup schedule",{error:t.message})}},N=()=>{const e=f();y&&(clearInterval(y),y=null,e.info("Backup schedule stopped"))};export{P as cleanup_old_backups,E as create_backup,S as get_s3_client,O as list_backups,V as restore_backup,J as start_backup_schedule,N as stop_backup_schedule,R as test_s3_connection};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import m from"./logger.js";const{create_context_logger:l}=m("connection_manager"),f=(u={})=>{const{max_connections:c=1e3,idle_timeout:_=600*1e3,request_timeout:r=5*1e3}=u,n=new Map,o=l(),d=e=>{if(n.size>=c)return o.warn("Connection limit reached",{current_connections:n.size,max_connections:c,rejected_socket_id:e.id}),e.destroy(),!1;const t={socket:e,connected_at:Date.now(),last_activity:Date.now(),idle_timer:null,request_count:0};return t.idle_timer=setTimeout(()=>{o.info("Connection idle timeout",{socket_id:e.id,idle_duration_ms:Date.now()-t.last_activity}),s(e.id),e.destroy()},_),n.set(e.id,t),o.info("Connection added",{socket_id:e.id,total_connections:n.size,max_connections:c}),!0},s=e=>{const t=n.get(e);t&&(t.idle_timer&&clearTimeout(t.idle_timer),n.delete(e),o.info("Connection removed",{socket_id:e,total_connections:n.size,connection_duration_ms:Date.now()-t.connected_at,request_count:t.request_count}))};return{add_connection:d,remove_connection:s,update_activity:e=>{const t=n.get(e);t&&(t.last_activity=Date.now(),t.request_count++,t.idle_timer&&clearTimeout(t.idle_timer),t.idle_timer=setTimeout(()=>{o.info("Connection idle timeout",{socket_id:e,idle_duration_ms:Date.now()-t.last_activity}),s(e),t.socket.destroy()},_))},create_request_timeout:(e,t)=>setTimeout(()=>{const i=n.get(e);i&&(o.warn("Request timeout",{socket_id:e,operation:t,timeout_ms:r}),s(e),i.socket.destroy())},r),get_stats:()=>{const e=Date.now();let t=0,i=e;for(const a of n.values())t+=a.request_count,i=Math.min(i,a.connected_at);return{total_connections:n.size,max_connections:c,total_requests:t,oldest_connection_age_ms:n.size>0?e-i:0,idle_timeout_ms:_,request_timeout_ms:r}},shutdown:()=>{o.info("Shutting down connection manager",{active_connections:n.size});for(const[e,t]of n)t.idle_timer&&clearTimeout(t.idle_timer),t.socket.destroy();n.clear()}}};export{f as create_connection_manager};
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{exec as g}from"child_process";import{promisify as u}from"util";import _ from"./logger.js";const d=u(g),{create_context_logger:c}=_("disk_utils"),i=async(t=".")=>{const o=c();try{const{stdout:e}=await d(`df -k ${t}`),r=e.trim().split(`
|
|
2
|
+
`);if(r.length<2)throw new Error("Unable to parse df output");const s=r[1].split(/\s+/);if(s.length<4)throw new Error("Invalid df output format");const a=parseInt(s[1]),n=a*1024;return o.info("Disk size detected",{path:t,total_kb:a,total_bytes:n,total_gb:Math.round(n/(1024*1024*1024)*100)/100}),n}catch(e){throw o.error("Failed to get disk size",{path:t,error:e.message}),e}},f=async(t="./data")=>{const o=c();try{const e=await i(t),r=Math.floor(e*.8);return o.info("Map size calculated",{database_path:t,disk_size:e,map_size:r,percentage:80}),r}catch(e){return o.warn("Failed to calculate map_size, using default",{database_path:t,error:e.message,default_size:1024*1024*1024*10}),1024*1024*1024*10}},m=(t,o,e)=>{if(o/t>=.8){const s=Math.min(t*2,Math.floor(e*.8));return s>t?s:null}return null};export{f as calculate_map_size,i as get_disk_size,m as should_grow_map_size};
|
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import v from"http";import{URL as x}from"url";import _ from"crypto";import k from"./logger.js";import{setup_authentication as T,get_auth_stats as P}from"./auth_manager.js";import{is_token_valid as C,record_failed_recovery_attempt as A,change_password as S}from"./recovery_manager.js";import{validate_api_key as I,create_user as D,get_all_users as E,update_user as H,delete_user as O}from"./api_key_manager.js";const{create_context_logger:R}=k("http_server"),a=R();let p=null,l=null,u=!1,m=new Map;const B=60*1e3,U=10,J=()=>_.randomUUID(),f=()=>!P().configured,Y=t=>{const e=Date.now(),r=(m.get(t)||[]).filter(n=>e-n<B);return m.set(t,r),r.length>=U},$=t=>{const e=Date.now(),o=m.get(t)||[];o.push(e),m.set(t,o)},j=(t,e=null)=>`<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<title>JoystickDB Setup</title>
|
|
5
|
+
<meta charset="utf-8">
|
|
6
|
+
<style>
|
|
7
|
+
body {
|
|
8
|
+
font-family: Arial, sans-serif;
|
|
9
|
+
max-width: 600px;
|
|
10
|
+
margin: 50px auto;
|
|
11
|
+
padding: 20px;
|
|
12
|
+
line-height: 1.6;
|
|
13
|
+
}
|
|
14
|
+
.container {
|
|
15
|
+
border: 1px solid #ddd;
|
|
16
|
+
padding: 30px;
|
|
17
|
+
border-radius: 5px;
|
|
18
|
+
background: #f9f9f9;
|
|
19
|
+
}
|
|
20
|
+
.button {
|
|
21
|
+
background: #007bff;
|
|
22
|
+
color: white;
|
|
23
|
+
padding: 10px 20px;
|
|
24
|
+
border: none;
|
|
25
|
+
border-radius: 3px;
|
|
26
|
+
cursor: pointer;
|
|
27
|
+
font-size: 16px;
|
|
28
|
+
}
|
|
29
|
+
.button:hover {
|
|
30
|
+
background: #0056b3;
|
|
31
|
+
}
|
|
32
|
+
.code {
|
|
33
|
+
background: #f4f4f4;
|
|
34
|
+
padding: 2px 6px;
|
|
35
|
+
border-radius: 3px;
|
|
36
|
+
font-family: monospace;
|
|
37
|
+
}
|
|
38
|
+
</style>
|
|
39
|
+
</head>
|
|
40
|
+
<body>
|
|
41
|
+
<div class="container">
|
|
42
|
+
<h1>JoystickDB Setup</h1>
|
|
43
|
+
${e?`<div style="color: red; margin-bottom: 20px; padding: 10px; border: 1px solid red; background: #ffe6e6;">${e}</div>`:""}
|
|
44
|
+
<p>Your JoystickDB server needs to be configured with authentication before it can be used.</p>
|
|
45
|
+
<p>Click the button below to generate a secure password and complete the setup process.</p>
|
|
46
|
+
|
|
47
|
+
<form method="POST" action="/setup?token=${t}">
|
|
48
|
+
<button type="submit" class="button">Setup JoystickDB</button>
|
|
49
|
+
</form>
|
|
50
|
+
|
|
51
|
+
<hr style="margin: 30px 0;">
|
|
52
|
+
|
|
53
|
+
<h3>What happens during setup?</h3>
|
|
54
|
+
<ul>
|
|
55
|
+
<li>A secure random password will be generated</li>
|
|
56
|
+
<li>Authentication configuration will be saved to <span class="code">settings.db.json</span></li>
|
|
57
|
+
<li>You'll receive the password to use with your client connections</li>
|
|
58
|
+
</ul>
|
|
59
|
+
|
|
60
|
+
<h3>After setup:</h3>
|
|
61
|
+
<ul>
|
|
62
|
+
<li>Set the environment variable: <span class="code">JOYSTICKDB_PASSWORD=<your_password></span></li>
|
|
63
|
+
<li>Connect your client to TCP port 1983</li>
|
|
64
|
+
<li>This setup interface will no longer be accessible</li>
|
|
65
|
+
</ul>
|
|
66
|
+
</div>
|
|
67
|
+
</body>
|
|
68
|
+
</html>`,N=t=>`<!DOCTYPE html>
|
|
69
|
+
<html>
|
|
70
|
+
<head>
|
|
71
|
+
<title>JoystickDB Setup Complete</title>
|
|
72
|
+
<meta charset="utf-8">
|
|
73
|
+
<style>
|
|
74
|
+
body {
|
|
75
|
+
font-family: Arial, sans-serif;
|
|
76
|
+
max-width: 600px;
|
|
77
|
+
margin: 50px auto;
|
|
78
|
+
padding: 20px;
|
|
79
|
+
line-height: 1.6;
|
|
80
|
+
}
|
|
81
|
+
.container {
|
|
82
|
+
border: 1px solid #28a745;
|
|
83
|
+
padding: 30px;
|
|
84
|
+
border-radius: 5px;
|
|
85
|
+
background: #d4edda;
|
|
86
|
+
}
|
|
87
|
+
.password {
|
|
88
|
+
background: #f8f9fa;
|
|
89
|
+
padding: 15px;
|
|
90
|
+
border: 1px solid #dee2e6;
|
|
91
|
+
border-radius: 5px;
|
|
92
|
+
font-family: monospace;
|
|
93
|
+
font-size: 18px;
|
|
94
|
+
font-weight: bold;
|
|
95
|
+
text-align: center;
|
|
96
|
+
margin: 20px 0;
|
|
97
|
+
word-break: break-all;
|
|
98
|
+
}
|
|
99
|
+
.code {
|
|
100
|
+
background: #f4f4f4;
|
|
101
|
+
padding: 2px 6px;
|
|
102
|
+
border-radius: 3px;
|
|
103
|
+
font-family: monospace;
|
|
104
|
+
}
|
|
105
|
+
.warning {
|
|
106
|
+
background: #fff3cd;
|
|
107
|
+
border: 1px solid #ffeaa7;
|
|
108
|
+
padding: 15px;
|
|
109
|
+
border-radius: 5px;
|
|
110
|
+
margin: 20px 0;
|
|
111
|
+
}
|
|
112
|
+
</style>
|
|
113
|
+
</head>
|
|
114
|
+
<body>
|
|
115
|
+
<div class="container">
|
|
116
|
+
<h1>\u2705 Setup Completed Successfully!</h1>
|
|
117
|
+
|
|
118
|
+
<div class="warning">
|
|
119
|
+
<strong>\u26A0\uFE0F Important:</strong> Save this password immediately. It will not be shown again.
|
|
120
|
+
</div>
|
|
121
|
+
|
|
122
|
+
<h3>Generated Password:</h3>
|
|
123
|
+
<div class="password">${t}</div>
|
|
124
|
+
|
|
125
|
+
<h3>Next Steps:</h3>
|
|
126
|
+
<ol>
|
|
127
|
+
<li>
|
|
128
|
+
<strong>Set environment variable:</strong><br>
|
|
129
|
+
<span class="code">JOYSTICKDB_PASSWORD=${t}</span>
|
|
130
|
+
</li>
|
|
131
|
+
<li>
|
|
132
|
+
<strong>Restart your application</strong> to use the new password
|
|
133
|
+
</li>
|
|
134
|
+
<li>
|
|
135
|
+
<strong>Connect using the TCP client on port 1983</strong>
|
|
136
|
+
</li>
|
|
137
|
+
</ol>
|
|
138
|
+
|
|
139
|
+
<h3>Client Connection Example:</h3>
|
|
140
|
+
<pre style="background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto;">
|
|
141
|
+
import joystickdb from '@joystickdb/client';
|
|
142
|
+
|
|
143
|
+
const client = joystickdb.client({
|
|
144
|
+
host: 'localhost',
|
|
145
|
+
port: 1983,
|
|
146
|
+
password: '${t}'
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await client.ping();
|
|
150
|
+
</pre>
|
|
151
|
+
|
|
152
|
+
<h3>Configuration Details:</h3>
|
|
153
|
+
<ul>
|
|
154
|
+
<li>Configuration saved to: <span class="code">settings.db.json</span></li>
|
|
155
|
+
<li>TCP server running on port: <span class="code">1983</span></li>
|
|
156
|
+
<li>This setup interface is now disabled</li>
|
|
157
|
+
</ul>
|
|
158
|
+
|
|
159
|
+
<p><strong>Your JoystickDB server is ready to use!</strong></p>
|
|
160
|
+
</div>
|
|
161
|
+
</body>
|
|
162
|
+
</html>`,c=t=>`<!DOCTYPE html>
|
|
163
|
+
<html>
|
|
164
|
+
<head>
|
|
165
|
+
<title>JoystickDB Setup Error</title>
|
|
166
|
+
<meta charset="utf-8">
|
|
167
|
+
<style>
|
|
168
|
+
body {
|
|
169
|
+
font-family: Arial, sans-serif;
|
|
170
|
+
max-width: 600px;
|
|
171
|
+
margin: 50px auto;
|
|
172
|
+
padding: 20px;
|
|
173
|
+
line-height: 1.6;
|
|
174
|
+
}
|
|
175
|
+
.container {
|
|
176
|
+
border: 1px solid #dc3545;
|
|
177
|
+
padding: 30px;
|
|
178
|
+
border-radius: 5px;
|
|
179
|
+
background: #f8d7da;
|
|
180
|
+
}
|
|
181
|
+
.code {
|
|
182
|
+
background: #f4f4f4;
|
|
183
|
+
padding: 2px 6px;
|
|
184
|
+
border-radius: 3px;
|
|
185
|
+
font-family: monospace;
|
|
186
|
+
}
|
|
187
|
+
</style>
|
|
188
|
+
</head>
|
|
189
|
+
<body>
|
|
190
|
+
<div class="container">
|
|
191
|
+
<h1>\u274C Setup Error</h1>
|
|
192
|
+
<p><strong>Error:</strong> ${t}</p>
|
|
193
|
+
|
|
194
|
+
<h3>Troubleshooting:</h3>
|
|
195
|
+
<ul>
|
|
196
|
+
<li>If authentication is already configured, delete the <span class="code">authentication</span> section from <span class="code">settings.db.json</span></li>
|
|
197
|
+
<li>Ensure the server has write permissions to the current directory</li>
|
|
198
|
+
<li>Check server logs for additional error details</li>
|
|
199
|
+
<li>Restart the server and try again</li>
|
|
200
|
+
</ul>
|
|
201
|
+
|
|
202
|
+
<p><a href="javascript:history.back()">\u2190 Go Back</a></p>
|
|
203
|
+
</div>
|
|
204
|
+
</body>
|
|
205
|
+
</html>`,g=(t,e=null)=>`<!DOCTYPE html>
|
|
206
|
+
<html>
|
|
207
|
+
<head>
|
|
208
|
+
<title>JoystickDB Emergency Password Recovery</title>
|
|
209
|
+
<meta charset="utf-8">
|
|
210
|
+
<style>
|
|
211
|
+
body {
|
|
212
|
+
font-family: Arial, sans-serif;
|
|
213
|
+
max-width: 600px;
|
|
214
|
+
margin: 50px auto;
|
|
215
|
+
padding: 20px;
|
|
216
|
+
line-height: 1.6;
|
|
217
|
+
}
|
|
218
|
+
.container {
|
|
219
|
+
border: 1px solid #ffc107;
|
|
220
|
+
padding: 30px;
|
|
221
|
+
border-radius: 5px;
|
|
222
|
+
background: #fff3cd;
|
|
223
|
+
}
|
|
224
|
+
.button {
|
|
225
|
+
background: #dc3545;
|
|
226
|
+
color: white;
|
|
227
|
+
padding: 10px 20px;
|
|
228
|
+
border: none;
|
|
229
|
+
border-radius: 3px;
|
|
230
|
+
cursor: pointer;
|
|
231
|
+
font-size: 16px;
|
|
232
|
+
}
|
|
233
|
+
.button:hover {
|
|
234
|
+
background: #c82333;
|
|
235
|
+
}
|
|
236
|
+
.form-group {
|
|
237
|
+
margin-bottom: 20px;
|
|
238
|
+
}
|
|
239
|
+
.form-group label {
|
|
240
|
+
display: block;
|
|
241
|
+
margin-bottom: 5px;
|
|
242
|
+
font-weight: bold;
|
|
243
|
+
}
|
|
244
|
+
.form-group input {
|
|
245
|
+
width: 100%;
|
|
246
|
+
padding: 10px;
|
|
247
|
+
border: 1px solid #ddd;
|
|
248
|
+
border-radius: 3px;
|
|
249
|
+
font-size: 16px;
|
|
250
|
+
box-sizing: border-box;
|
|
251
|
+
}
|
|
252
|
+
.warning {
|
|
253
|
+
background: #f8d7da;
|
|
254
|
+
border: 1px solid #f5c6cb;
|
|
255
|
+
padding: 15px;
|
|
256
|
+
border-radius: 5px;
|
|
257
|
+
margin: 20px 0;
|
|
258
|
+
}
|
|
259
|
+
.code {
|
|
260
|
+
background: #f4f4f4;
|
|
261
|
+
padding: 2px 6px;
|
|
262
|
+
border-radius: 3px;
|
|
263
|
+
font-family: monospace;
|
|
264
|
+
}
|
|
265
|
+
</style>
|
|
266
|
+
<script>
|
|
267
|
+
function validateForm() {
|
|
268
|
+
const password = document.getElementById('password').value;
|
|
269
|
+
const confirmPassword = document.getElementById('confirm_password').value;
|
|
270
|
+
|
|
271
|
+
if (password.length < 12) {
|
|
272
|
+
alert('Password must be at least 12 characters long');
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
if (password !== confirmPassword) {
|
|
277
|
+
alert('Passwords do not match');
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
return confirm('Are you sure you want to change the database password? This will disconnect all existing clients.');
|
|
282
|
+
}
|
|
283
|
+
</script>
|
|
284
|
+
</head>
|
|
285
|
+
<body>
|
|
286
|
+
<div class="container">
|
|
287
|
+
<h1>\u{1F6A8} Emergency Password Recovery</h1>
|
|
288
|
+
${e?`<div style="color: red; margin-bottom: 20px; padding: 10px; border: 1px solid red; background: #ffe6e6;">${e}</div>`:""}
|
|
289
|
+
|
|
290
|
+
<div class="warning">
|
|
291
|
+
<strong>\u26A0\uFE0F Warning:</strong> This will change your database password and disconnect all existing clients.
|
|
292
|
+
</div>
|
|
293
|
+
|
|
294
|
+
<p>Enter a new password for your JoystickDB server. The password must be at least 12 characters long.</p>
|
|
295
|
+
|
|
296
|
+
<form method="POST" action="/recovery?token=${t}" onsubmit="return validateForm()">
|
|
297
|
+
<div class="form-group">
|
|
298
|
+
<label for="password">New Password:</label>
|
|
299
|
+
<input type="password" id="password" name="password" required minlength="12" placeholder="Enter new password (minimum 12 characters)">
|
|
300
|
+
</div>
|
|
301
|
+
|
|
302
|
+
<div class="form-group">
|
|
303
|
+
<label for="confirm_password">Confirm Password:</label>
|
|
304
|
+
<input type="password" id="confirm_password" name="confirm_password" required minlength="12" placeholder="Confirm new password">
|
|
305
|
+
</div>
|
|
306
|
+
|
|
307
|
+
<button type="submit" class="button">Change Password</button>
|
|
308
|
+
</form>
|
|
309
|
+
|
|
310
|
+
<hr style="margin: 30px 0;">
|
|
311
|
+
|
|
312
|
+
<h3>What happens when you change the password?</h3>
|
|
313
|
+
<ul>
|
|
314
|
+
<li>The new password will be hashed and stored securely</li>
|
|
315
|
+
<li>All existing client connections will be terminated</li>
|
|
316
|
+
<li>Failed authentication attempts will be reset</li>
|
|
317
|
+
<li>This recovery token will be invalidated</li>
|
|
318
|
+
</ul>
|
|
319
|
+
|
|
320
|
+
<h3>After password change:</h3>
|
|
321
|
+
<ul>
|
|
322
|
+
<li>Update your environment variable: <span class="code">JOYSTICKDB_PASSWORD=<new_password></span></li>
|
|
323
|
+
<li>Restart or redeploy your applications</li>
|
|
324
|
+
<li>Monitor logs for any unauthorized access attempts</li>
|
|
325
|
+
</ul>
|
|
326
|
+
</div>
|
|
327
|
+
</body>
|
|
328
|
+
</html>`,W=t=>`<!DOCTYPE html>
|
|
329
|
+
<html>
|
|
330
|
+
<head>
|
|
331
|
+
<title>JoystickDB Password Changed</title>
|
|
332
|
+
<meta charset="utf-8">
|
|
333
|
+
<style>
|
|
334
|
+
body {
|
|
335
|
+
font-family: Arial, sans-serif;
|
|
336
|
+
max-width: 600px;
|
|
337
|
+
margin: 50px auto;
|
|
338
|
+
padding: 20px;
|
|
339
|
+
line-height: 1.6;
|
|
340
|
+
}
|
|
341
|
+
.container {
|
|
342
|
+
border: 1px solid #28a745;
|
|
343
|
+
padding: 30px;
|
|
344
|
+
border-radius: 5px;
|
|
345
|
+
background: #d4edda;
|
|
346
|
+
}
|
|
347
|
+
.code {
|
|
348
|
+
background: #f4f4f4;
|
|
349
|
+
padding: 2px 6px;
|
|
350
|
+
border-radius: 3px;
|
|
351
|
+
font-family: monospace;
|
|
352
|
+
}
|
|
353
|
+
.warning {
|
|
354
|
+
background: #fff3cd;
|
|
355
|
+
border: 1px solid #ffeaa7;
|
|
356
|
+
padding: 15px;
|
|
357
|
+
border-radius: 5px;
|
|
358
|
+
margin: 20px 0;
|
|
359
|
+
}
|
|
360
|
+
</style>
|
|
361
|
+
</head>
|
|
362
|
+
<body>
|
|
363
|
+
<div class="container">
|
|
364
|
+
<h1>\u2705 Password Changed Successfully!</h1>
|
|
365
|
+
|
|
366
|
+
<p>New password is now active.</p>
|
|
367
|
+
<p>All existing connections have been terminated.</p>
|
|
368
|
+
|
|
369
|
+
<div class="warning">
|
|
370
|
+
<strong>\u26A0\uFE0F Important:</strong> Update your applications immediately to use the new password.
|
|
371
|
+
</div>
|
|
372
|
+
|
|
373
|
+
<h3>Next steps:</h3>
|
|
374
|
+
<ol>
|
|
375
|
+
<li>
|
|
376
|
+
<strong>Update your app's environment settings</strong> so the client can connect using your new password:<br>
|
|
377
|
+
<span class="code">JOYSTICKDB_PASSWORD=<your_new_password></span>
|
|
378
|
+
</li>
|
|
379
|
+
<li>
|
|
380
|
+
<strong>Restart or redeploy your applications</strong> to use new password
|
|
381
|
+
</li>
|
|
382
|
+
<li>
|
|
383
|
+
<strong>Monitor logs</strong> for any unauthorized access attempts
|
|
384
|
+
</li>
|
|
385
|
+
</ol>
|
|
386
|
+
|
|
387
|
+
<h3>Recovery Details:</h3>
|
|
388
|
+
<ul>
|
|
389
|
+
<li>Recovery completed at: <span class="code">${t}</span></li>
|
|
390
|
+
<li>All failed authentication attempts have been reset</li>
|
|
391
|
+
<li>Recovery token has been invalidated</li>
|
|
392
|
+
<li>TCP server continues running on port 1983</li>
|
|
393
|
+
</ul>
|
|
394
|
+
|
|
395
|
+
<p><strong>Your JoystickDB server is ready with the new password!</strong></p>
|
|
396
|
+
</div>
|
|
397
|
+
</body>
|
|
398
|
+
</html>`,M=t=>new Promise((e,o)=>{let r="";t.on("data",n=>{r+=n.toString()}),t.on("end",()=>{try{const n=new URLSearchParams(r),s={};for(const[d,h]of n)s[d]=h;e(s)}catch(n){o(n)}}),t.on("error",n=>{o(n)})}),b=t=>new Promise((e,o)=>{let r="";t.on("data",n=>{r+=n.toString()}),t.on("end",()=>{try{const n=JSON.parse(r);e(n)}catch(n){o(n)}}),t.on("error",n=>{o(n)})}),y=t=>{const e=t.headers["x-joystick-db-api-key"];return I(e)},i=(t,e,o)=>{t.writeHead(e,{"Content-Type":"application/json"}),t.end(JSON.stringify(o))},G=async(t,e)=>{const o=t.socket.remoteAddress||"127.0.0.1";if(!y(t)){a.warn("Invalid API key for user creation",{client_ip:o}),i(e,403,{error:"Database setup incomplete. A valid API key must be passed until an admin user has been created."});return}try{const r=await b(t),n=await D(r);a.info("User created via API",{username:n.username,client_ip:o}),i(e,201,{ok:1,user:n})}catch(r){a.error("User creation failed via API",{client_ip:o,error:r.message}),i(e,400,{error:r.message})}},z=async(t,e)=>{const o=t.socket.remoteAddress||"127.0.0.1";if(!y(t)){a.warn("Invalid API key for get users",{client_ip:o}),i(e,403,{error:"Database setup incomplete. A valid API key must be passed until an admin user has been created."});return}try{const r=E();a.info("Users retrieved via API",{count:r.length,client_ip:o}),i(e,200,{ok:1,users:r})}catch(r){a.error("Get users failed via API",{client_ip:o,error:r.message}),i(e,500,{error:r.message})}},F=async(t,e,o)=>{const r=t.socket.remoteAddress||"127.0.0.1";if(!y(t)){a.warn("Invalid API key for user update",{client_ip:r,username:o}),i(e,403,{error:"Database setup incomplete. A valid API key must be passed until an admin user has been created."});return}try{const n=await b(t),s=await H(o,n);a.info("User updated via API",{username:o,client_ip:r}),i(e,200,{ok:1,user:s})}catch(n){a.error("User update failed via API",{client_ip:r,username:o,error:n.message});const s=n.message==="User not found"?404:400;i(e,s,{error:n.message})}},K=async(t,e,o)=>{const r=t.socket.remoteAddress||"127.0.0.1";if(!y(t)){a.warn("Invalid API key for user deletion",{client_ip:r,username:o}),i(e,403,{error:"Database setup incomplete. A valid API key must be passed until an admin user has been created."});return}try{O(o),a.info("User deleted via API",{username:o,client_ip:r}),i(e,200,{ok:1,message:"User deleted successfully"})}catch(n){a.error("User deletion failed via API",{client_ip:r,username:o,error:n.message});const s=n.message==="User not found"?404:400;i(e,s,{error:n.message})}},L=async(t,e,o)=>{if(t.method==="POST"&&o.length===0){await G(t,e);return}if(t.method==="GET"&&o.length===0){await z(t,e);return}if(t.method==="PUT"&&o.length===1){const r=o[0];await F(t,e,r);return}if(t.method==="DELETE"&&o.length===1){const r=o[0];await K(t,e,r);return}i(e,405,{error:"Method not allowed"})},V=async(t,e,o)=>{const r=t.socket.remoteAddress||"127.0.0.1";if(Y(r)){e.writeHead(429,{"Content-Type":"text/html"}),e.end(c("Too many setup attempts. Please try again later."));return}if($(r),!f()){e.writeHead(400,{"Content-Type":"text/html"}),e.end(c("Setup has already been completed."));return}if(!l||o!==l){a.warn("Invalid setup token attempt",{client_ip:r,provided_token:o}),e.writeHead(403,{"Content-Type":"text/html"}),e.end(c("Invalid or missing setup token."));return}if(t.method==="GET"){e.writeHead(200,{"Content-Type":"text/html"}),e.end(j(l));return}if(t.method==="POST"){try{const n=T();u=!0,l=null,a.info("Setup completed successfully via HTTP interface",{client_ip:r}),e.writeHead(200,{"Content-Type":"text/html"}),e.end(N(n))}catch(n){a.error("Setup failed via HTTP interface",{client_ip:r,error:n.message}),e.writeHead(500,{"Content-Type":"text/html"}),e.end(c(n.message))}return}e.writeHead(405,{"Content-Type":"text/html"}),e.end(c("Method not allowed."))},X=async(t,e,o)=>{const r=t.socket.remoteAddress||"127.0.0.1";if(a.info("Recovery request received",{client_ip:r,method:t.method}),!o){e.writeHead(400,{"Content-Type":"text/html"}),e.end(c("Recovery token is required."));return}const n=C(o);if(!n.valid){A(r);let s="Invalid recovery token.";n.reason==="expired"?s="Recovery token has expired. Generate a new token using --generate-recovery-token.":n.reason==="locked"?s="Recovery is locked due to too many failed attempts. Please try again later.":n.reason==="no_token"&&(s="No active recovery token found. Generate a new token using --generate-recovery-token."),a.warn("Invalid recovery token attempt",{client_ip:r,reason:n.reason,provided_token:o}),e.writeHead(403,{"Content-Type":"text/html"}),e.end(c(s));return}if(t.method==="GET"){e.writeHead(200,{"Content-Type":"text/html"}),e.end(g(o));return}if(t.method==="POST"){try{const s=await M(t),{password:d,confirm_password:h}=s;if(!d||!h){e.writeHead(400,{"Content-Type":"text/html"}),e.end(g(o,"Both password fields are required."));return}if(d!==h){e.writeHead(400,{"Content-Type":"text/html"}),e.end(g(o,"Passwords do not match."));return}const w=await S(d,r,()=>{a.info("Password change completed, existing connections should be terminated")});a.info("Emergency password change completed via HTTP interface",{client_ip:r,timestamp:w.timestamp}),e.writeHead(200,{"Content-Type":"text/html"}),e.end(W(w.timestamp))}catch(s){a.error("Emergency password change failed via HTTP interface",{client_ip:r,error:s.message}),e.writeHead(400,{"Content-Type":"text/html"}),e.end(g(o,s.message))}return}e.writeHead(405,{"Content-Type":"text/html"}),e.end(c("Method not allowed."))},Q=(t=1984)=>{const e=v.createServer(async(r,n)=>{try{const s=new x(r.url,`http://localhost:${t}`);if(s.pathname==="/setup"){const d=s.searchParams.get("token");await V(r,n,d);return}if(s.pathname==="/recovery"){const d=s.searchParams.get("token");await X(r,n,d);return}if(s.pathname.startsWith("/api/users")){const d=s.pathname.split("/").slice(3);await L(r,n,d);return}n.writeHead(404,{"Content-Type":"text/html"}),n.end(`<!DOCTYPE html>
|
|
399
|
+
<html>
|
|
400
|
+
<head><title>404 Not Found</title></head>
|
|
401
|
+
<body>
|
|
402
|
+
<h1>404 Not Found</h1>
|
|
403
|
+
<p>The requested resource was not found on this server.</p>
|
|
404
|
+
</body>
|
|
405
|
+
</html>`)}catch(s){a.error("HTTP request error",{error:s.message,url:r.url}),n.writeHead(500,{"Content-Type":"text/html"}),n.end(c("Internal server error."))}}),o=new Set;return e.on("connection",r=>{o.add(r),r.on("close",()=>{o.delete(r)})}),e._connections=o,e.on("error",r=>{a.error("HTTP server error",{error:r.message})}),e},Z=(t=1984)=>{const e=f();e&&(l=J(),u=!1);const o=Q(t);return new Promise((r,n)=>{o.once("error",s=>{e&&(l=null,u=!1),a.error("Failed to start HTTP server",{port:t,error:s.message}),n(s)}),o.listen(t,()=>{p=o,e?(a.info("JoystickDB Setup Required"),a.info(`Visit: http://localhost:${t}/setup?token=${l}`)):a.info("HTTP server started for recovery operations",{port:t}),r(o)})})},q=()=>new Promise(t=>{if(!p){t();return}const e=p,o=e._connections||new Set;p=null,l=null,u=!1,m.clear(),o.forEach(r=>{try{r.destroy()}catch{}}),e.close(r=>{r?a.warn("HTTP server close error",{error:r.message}):a.info("HTTP server stopped"),setTimeout(()=>{t()},250)}),setTimeout(()=>{a.warn("HTTP server forced shutdown after timeout"),t()},2e3)}),ee=()=>({setup_required:f(),setup_token:l,setup_completed:u,http_server_running:!!p});export{ee as get_setup_info,f as is_setup_required,Z as start_http_server,q as stop_http_server};
|