@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
|
@@ -0,0 +1,756 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Index management system for JoystickDB providing secondary indexing capabilities.
|
|
3
|
+
* Handles creation, maintenance, and querying of database indexes with support for unique and sparse indexes.
|
|
4
|
+
* Manages index metadata, automatic index updates on document changes, and index-based query optimization.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { get_database } from './query_engine.js';
|
|
8
|
+
import create_logger from './logger.js';
|
|
9
|
+
|
|
10
|
+
const { create_context_logger } = create_logger('index_manager');
|
|
11
|
+
|
|
12
|
+
/** @type {Object|null} LMDB database instance for storing index data */
|
|
13
|
+
let index_db = null;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Initializes the index database by opening the 'indexes' sub-database.
|
|
17
|
+
* Always reinitializes to ensure fresh reference after database restarts.
|
|
18
|
+
* @returns {Object} LMDB database instance for indexes
|
|
19
|
+
* @throws {Error} When main database is not initialized
|
|
20
|
+
*/
|
|
21
|
+
const initialize_index_database = () => {
|
|
22
|
+
// Always reinitialize to ensure we have a fresh reference
|
|
23
|
+
// This handles cases where the main database was closed and reopened
|
|
24
|
+
try {
|
|
25
|
+
const main_db = get_database();
|
|
26
|
+
index_db = main_db.openDB('indexes', { create: true });
|
|
27
|
+
} catch (error) {
|
|
28
|
+
// If main database is not initialized, throw a clear error
|
|
29
|
+
throw new Error('Main database not initialized. Call initialize_database first.');
|
|
30
|
+
}
|
|
31
|
+
return index_db;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Gets the initialized index database instance.
|
|
36
|
+
* @returns {Object} LMDB database instance for indexes
|
|
37
|
+
* @throws {Error} When index database is not initialized
|
|
38
|
+
*/
|
|
39
|
+
const get_index_database = () => {
|
|
40
|
+
if (!index_db) {
|
|
41
|
+
throw new Error('Index database not initialized. Call initialize_index_database first.');
|
|
42
|
+
}
|
|
43
|
+
return index_db;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Builds a standardized index key for storing index entries.
|
|
48
|
+
* @param {string} database_name - Name of the database
|
|
49
|
+
* @param {string} collection_name - Name of the collection
|
|
50
|
+
* @param {string} field_name - Name of the indexed field
|
|
51
|
+
* @param {*} field_value - Value to index (will be stringified)
|
|
52
|
+
* @returns {string} Formatted index key
|
|
53
|
+
*/
|
|
54
|
+
const build_index_key = (database_name, collection_name, field_name, field_value) => {
|
|
55
|
+
const value_str = typeof field_value === 'object' ? JSON.stringify(field_value) : String(field_value);
|
|
56
|
+
return `index:${database_name}:${collection_name}:${field_name}:${value_str}`;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* Parses an index key to extract database, collection, field, and value components.
|
|
61
|
+
* @param {string} key - Index key to parse
|
|
62
|
+
* @returns {Object|null} Parsed components or null if invalid format
|
|
63
|
+
* @returns {string} returns.database - Database name
|
|
64
|
+
* @returns {string} returns.collection - Collection name
|
|
65
|
+
* @returns {string} returns.field - Field name
|
|
66
|
+
* @returns {string} returns.value - Field value as string
|
|
67
|
+
*/
|
|
68
|
+
const parse_index_key = (key) => {
|
|
69
|
+
const parts = key.split(':');
|
|
70
|
+
if (parts.length < 5 || parts[0] !== 'index') {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
database: parts[1],
|
|
76
|
+
collection: parts[2],
|
|
77
|
+
field: parts[3],
|
|
78
|
+
value: parts.slice(4).join(':')
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Builds a metadata key for storing index configuration.
|
|
84
|
+
* @param {string} database_name - Name of the database
|
|
85
|
+
* @param {string} collection_name - Name of the collection
|
|
86
|
+
* @param {string} field_name - Name of the indexed field
|
|
87
|
+
* @returns {string} Formatted metadata key
|
|
88
|
+
*/
|
|
89
|
+
const build_index_metadata_key = (database_name, collection_name, field_name) => {
|
|
90
|
+
return `meta:${database_name}:${collection_name}:${field_name}`;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* Extracts field value from document using dot notation path.
|
|
95
|
+
* @param {Object} document - Document to extract value from
|
|
96
|
+
* @param {string} field_path - Dot-separated field path (e.g., 'user.name')
|
|
97
|
+
* @returns {*} Field value or undefined if path doesn't exist
|
|
98
|
+
*/
|
|
99
|
+
const get_field_value = (document, field_path) => {
|
|
100
|
+
const parts = field_path.split('.');
|
|
101
|
+
let value = document;
|
|
102
|
+
|
|
103
|
+
for (const part of parts) {
|
|
104
|
+
if (value === null || value === undefined) {
|
|
105
|
+
return undefined;
|
|
106
|
+
}
|
|
107
|
+
value = value[part];
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return value;
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Compares existing index definition with new options to detect changes.
|
|
115
|
+
* @param {Object} existing - Existing index metadata
|
|
116
|
+
* @param {Object} new_options - New index options
|
|
117
|
+
* @returns {boolean} True if definitions match, false otherwise
|
|
118
|
+
*/
|
|
119
|
+
const compare_index_definitions = (existing, new_options) => {
|
|
120
|
+
return (
|
|
121
|
+
existing.unique === (new_options.unique || false) &&
|
|
122
|
+
existing.sparse === (new_options.sparse || false)
|
|
123
|
+
);
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Rebuilds an index by clearing existing entries and re-indexing all documents.
|
|
128
|
+
* @param {string} database_name - Name of the database
|
|
129
|
+
* @param {string} collection_name - Name of the collection
|
|
130
|
+
* @param {string} field_name - Name of the field to index
|
|
131
|
+
* @param {Object} options - Index options (unique, sparse)
|
|
132
|
+
* @param {Object} index_db - Index database instance
|
|
133
|
+
* @param {Object} main_db - Main database instance
|
|
134
|
+
* @throws {Error} When unique constraint violations are found
|
|
135
|
+
*/
|
|
136
|
+
const rebuild_index = (database_name, collection_name, field_name, options, index_db, main_db) => {
|
|
137
|
+
const index_prefix = `index:${database_name}:${collection_name}:${field_name}:`;
|
|
138
|
+
const range = index_db.getRange({ start: index_prefix, end: index_prefix + '\xFF' });
|
|
139
|
+
|
|
140
|
+
for (const { key } of range) {
|
|
141
|
+
index_db.remove(key);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const collection_prefix = `${database_name}:${collection_name}:`;
|
|
145
|
+
const document_range = main_db.getRange({ start: collection_prefix, end: collection_prefix + '\xFF' });
|
|
146
|
+
|
|
147
|
+
// First pass: check for unique constraint violations if unique index
|
|
148
|
+
if (options.unique) {
|
|
149
|
+
const value_counts = new Map();
|
|
150
|
+
|
|
151
|
+
for (const { key, value: document_data } of document_range) {
|
|
152
|
+
const document = JSON.parse(document_data);
|
|
153
|
+
const field_value = get_field_value(document, field_name);
|
|
154
|
+
|
|
155
|
+
if (field_value !== undefined && field_value !== null) {
|
|
156
|
+
const value_str = typeof field_value === 'object' ? JSON.stringify(field_value) : String(field_value);
|
|
157
|
+
const count = value_counts.get(value_str) || 0;
|
|
158
|
+
value_counts.set(value_str, count + 1);
|
|
159
|
+
|
|
160
|
+
if (count >= 1) {
|
|
161
|
+
throw new Error(`Duplicate value for unique index on ${database_name}.${collection_name}.${field_name}: ${field_value}`);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Second pass: rebuild the index
|
|
168
|
+
const document_range_rebuild = main_db.getRange({ start: collection_prefix, end: collection_prefix + '\xFF' });
|
|
169
|
+
|
|
170
|
+
for (const { key, value: document_data } of document_range_rebuild) {
|
|
171
|
+
const document = JSON.parse(document_data);
|
|
172
|
+
const field_value = get_field_value(document, field_name);
|
|
173
|
+
|
|
174
|
+
if (field_value === undefined || field_value === null) {
|
|
175
|
+
if (!options.sparse) {
|
|
176
|
+
const index_key = build_index_key(database_name, collection_name, field_name, null);
|
|
177
|
+
const existing_ids = index_db.get(index_key) || [];
|
|
178
|
+
existing_ids.push(document._id);
|
|
179
|
+
index_db.put(index_key, existing_ids);
|
|
180
|
+
}
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const index_key = build_index_key(database_name, collection_name, field_name, field_value);
|
|
185
|
+
const existing_ids = index_db.get(index_key) || [];
|
|
186
|
+
existing_ids.push(document._id);
|
|
187
|
+
index_db.put(index_key, existing_ids);
|
|
188
|
+
}
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Creates or updates an index on a collection field with support for unique and sparse options.
|
|
193
|
+
* @param {string} database_name - Name of the database
|
|
194
|
+
* @param {string} collection_name - Name of the collection to index
|
|
195
|
+
* @param {string} field_name - Name of the field to index
|
|
196
|
+
* @param {Object} [options={}] - Index creation options
|
|
197
|
+
* @param {boolean} [options.unique=false] - Whether index should enforce uniqueness
|
|
198
|
+
* @param {boolean} [options.sparse=false] - Whether to exclude null/undefined values
|
|
199
|
+
* @param {boolean} [options.upsert=false] - Whether to update existing index if it exists
|
|
200
|
+
* @returns {Promise<Object>} Result object with acknowledgment and operation type
|
|
201
|
+
* @returns {boolean} returns.acknowledged - Whether operation was acknowledged
|
|
202
|
+
* @returns {string} returns.operation_type - Type of operation performed (created/updated/unchanged)
|
|
203
|
+
* @throws {Error} When database/collection/field name missing, index exists without upsert, or unique constraint violations
|
|
204
|
+
*/
|
|
205
|
+
const create_index = async (database_name, collection_name, field_name, options = {}) => {
|
|
206
|
+
const log = create_context_logger();
|
|
207
|
+
|
|
208
|
+
if (!database_name) {
|
|
209
|
+
throw new Error('Database name is required');
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!collection_name) {
|
|
213
|
+
throw new Error('Collection name is required');
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
if (!field_name) {
|
|
217
|
+
throw new Error('Field name is required');
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
const index_db = get_index_database();
|
|
221
|
+
const main_db = get_database();
|
|
222
|
+
const metadata_key = build_index_metadata_key(database_name, collection_name, field_name);
|
|
223
|
+
const upsert = options.upsert || false;
|
|
224
|
+
|
|
225
|
+
try {
|
|
226
|
+
let operation_type = 'created';
|
|
227
|
+
|
|
228
|
+
await index_db.transaction(() => {
|
|
229
|
+
const existing_index = index_db.get(metadata_key);
|
|
230
|
+
|
|
231
|
+
if (existing_index) {
|
|
232
|
+
if (!upsert) {
|
|
233
|
+
throw new Error(`Index on ${database_name}.${collection_name}.${field_name} already exists`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const definitions_match = compare_index_definitions(existing_index, options);
|
|
237
|
+
|
|
238
|
+
if (definitions_match) {
|
|
239
|
+
operation_type = 'unchanged';
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
operation_type = 'updated';
|
|
244
|
+
try {
|
|
245
|
+
rebuild_index(database_name, collection_name, field_name, options, index_db, main_db);
|
|
246
|
+
} catch (rebuild_error) {
|
|
247
|
+
throw rebuild_error;
|
|
248
|
+
}
|
|
249
|
+
} else {
|
|
250
|
+
const collection_prefix = `${database_name}:${collection_name}:`;
|
|
251
|
+
const range = main_db.getRange({ start: collection_prefix, end: collection_prefix + '\xFF' });
|
|
252
|
+
|
|
253
|
+
for (const { key, value: document_data } of range) {
|
|
254
|
+
const document = JSON.parse(document_data);
|
|
255
|
+
const field_value = get_field_value(document, field_name);
|
|
256
|
+
|
|
257
|
+
if (field_value === undefined || field_value === null) {
|
|
258
|
+
if (!options.sparse) {
|
|
259
|
+
const index_key = build_index_key(database_name, collection_name, field_name, null);
|
|
260
|
+
const existing_ids = index_db.get(index_key) || [];
|
|
261
|
+
existing_ids.push(document._id);
|
|
262
|
+
index_db.put(index_key, existing_ids);
|
|
263
|
+
}
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (options.unique) {
|
|
268
|
+
const index_key = build_index_key(database_name, collection_name, field_name, field_value);
|
|
269
|
+
const existing_ids = index_db.get(index_key) || [];
|
|
270
|
+
|
|
271
|
+
if (existing_ids.length > 0) {
|
|
272
|
+
throw new Error(`Duplicate value for unique index on ${database_name}.${collection_name}.${field_name}: ${field_value}`);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
const index_key = build_index_key(database_name, collection_name, field_name, field_value);
|
|
277
|
+
const existing_ids = index_db.get(index_key) || [];
|
|
278
|
+
existing_ids.push(document._id);
|
|
279
|
+
index_db.put(index_key, existing_ids);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const index_metadata = {
|
|
284
|
+
database: database_name,
|
|
285
|
+
collection: collection_name,
|
|
286
|
+
field: field_name,
|
|
287
|
+
unique: options.unique || false,
|
|
288
|
+
sparse: options.sparse || false,
|
|
289
|
+
created_at: existing_index ? existing_index.created_at : new Date().toISOString(),
|
|
290
|
+
updated_at: operation_type === 'updated' ? new Date().toISOString() : undefined
|
|
291
|
+
};
|
|
292
|
+
|
|
293
|
+
index_db.put(metadata_key, index_metadata);
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
log.info(`Index ${operation_type} successfully`, {
|
|
297
|
+
database: database_name,
|
|
298
|
+
collection: collection_name,
|
|
299
|
+
field: field_name,
|
|
300
|
+
options,
|
|
301
|
+
operation_type
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
return { acknowledged: true, operation_type };
|
|
305
|
+
} catch (error) {
|
|
306
|
+
log.error('Failed to create/upsert index', {
|
|
307
|
+
database: database_name,
|
|
308
|
+
collection: collection_name,
|
|
309
|
+
field: field_name,
|
|
310
|
+
error: error.message
|
|
311
|
+
});
|
|
312
|
+
throw error;
|
|
313
|
+
}
|
|
314
|
+
};
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* Drops an existing index from a collection field.
|
|
318
|
+
* @param {string} database_name - Name of the database
|
|
319
|
+
* @param {string} collection_name - Name of the collection
|
|
320
|
+
* @param {string} field_name - Name of the indexed field
|
|
321
|
+
* @returns {Promise<Object>} Result object with acknowledgment
|
|
322
|
+
* @returns {boolean} returns.acknowledged - Whether operation was acknowledged
|
|
323
|
+
* @throws {Error} When database/collection/field name missing or index doesn't exist
|
|
324
|
+
*/
|
|
325
|
+
const drop_index = async (database_name, collection_name, field_name) => {
|
|
326
|
+
const log = create_context_logger();
|
|
327
|
+
|
|
328
|
+
if (!database_name) {
|
|
329
|
+
throw new Error('Database name is required');
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
if (!collection_name) {
|
|
333
|
+
throw new Error('Collection name is required');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
if (!field_name) {
|
|
337
|
+
throw new Error('Field name is required');
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
const index_db = get_index_database();
|
|
341
|
+
const metadata_key = build_index_metadata_key(database_name, collection_name, field_name);
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
await index_db.transaction(() => {
|
|
345
|
+
const existing_index = index_db.get(metadata_key);
|
|
346
|
+
|
|
347
|
+
if (!existing_index) {
|
|
348
|
+
throw new Error(`Index on ${database_name}.${collection_name}.${field_name} does not exist`);
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
index_db.remove(metadata_key);
|
|
352
|
+
|
|
353
|
+
const index_prefix = `index:${database_name}:${collection_name}:${field_name}:`;
|
|
354
|
+
const range = index_db.getRange({ start: index_prefix, end: index_prefix + '\xFF' });
|
|
355
|
+
|
|
356
|
+
for (const { key } of range) {
|
|
357
|
+
index_db.remove(key);
|
|
358
|
+
}
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
log.info('Index dropped successfully', {
|
|
362
|
+
database: database_name,
|
|
363
|
+
collection: collection_name,
|
|
364
|
+
field: field_name
|
|
365
|
+
});
|
|
366
|
+
|
|
367
|
+
return { acknowledged: true };
|
|
368
|
+
} catch (error) {
|
|
369
|
+
log.error('Failed to drop index', {
|
|
370
|
+
database: database_name,
|
|
371
|
+
collection: collection_name,
|
|
372
|
+
field: field_name,
|
|
373
|
+
error: error.message
|
|
374
|
+
});
|
|
375
|
+
throw error;
|
|
376
|
+
}
|
|
377
|
+
};
|
|
378
|
+
|
|
379
|
+
/**
|
|
380
|
+
* Retrieves all indexes for a given collection.
|
|
381
|
+
* @param {string} database_name - Name of the database
|
|
382
|
+
* @param {string} collection_name - Name of the collection
|
|
383
|
+
* @returns {Array<Object>} Array of index metadata objects
|
|
384
|
+
* @throws {Error} When database/collection name is missing or retrieval fails
|
|
385
|
+
*/
|
|
386
|
+
const get_indexes = (database_name, collection_name) => {
|
|
387
|
+
const log = create_context_logger();
|
|
388
|
+
|
|
389
|
+
if (!database_name) {
|
|
390
|
+
throw new Error('Database name is required');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
if (!collection_name) {
|
|
394
|
+
throw new Error('Collection name is required');
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
const index_db = get_index_database();
|
|
398
|
+
const indexes = [];
|
|
399
|
+
|
|
400
|
+
try {
|
|
401
|
+
const metadata_prefix = `meta:${database_name}:${collection_name}:`;
|
|
402
|
+
const range = index_db.getRange({ start: metadata_prefix, end: metadata_prefix + '\xFF' });
|
|
403
|
+
|
|
404
|
+
for (const { key, value: metadata } of range) {
|
|
405
|
+
indexes.push(metadata);
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
log.info('Retrieved indexes', {
|
|
409
|
+
database: database_name,
|
|
410
|
+
collection: collection_name,
|
|
411
|
+
count: indexes.length
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
return indexes;
|
|
415
|
+
} catch (error) {
|
|
416
|
+
log.error('Failed to get indexes', {
|
|
417
|
+
database: database_name,
|
|
418
|
+
collection: collection_name,
|
|
419
|
+
error: error.message
|
|
420
|
+
});
|
|
421
|
+
throw error;
|
|
422
|
+
}
|
|
423
|
+
};
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Updates all relevant indexes when a new document is inserted.
|
|
427
|
+
* @param {string} database_name - Name of the database
|
|
428
|
+
* @param {string} collection_name - Name of the collection
|
|
429
|
+
* @param {Object} document - Document being inserted
|
|
430
|
+
* @returns {Promise<void>} Promise that resolves when indexes are updated
|
|
431
|
+
* @throws {Error} When unique constraint violations occur or update fails
|
|
432
|
+
*/
|
|
433
|
+
const update_indexes_on_insert = async (database_name, collection_name, document) => {
|
|
434
|
+
const log = create_context_logger();
|
|
435
|
+
const index_db = get_index_database();
|
|
436
|
+
|
|
437
|
+
try {
|
|
438
|
+
const indexes = get_indexes(database_name, collection_name);
|
|
439
|
+
|
|
440
|
+
await index_db.transaction(() => {
|
|
441
|
+
for (const index_metadata of indexes) {
|
|
442
|
+
const field_value = get_field_value(document, index_metadata.field);
|
|
443
|
+
|
|
444
|
+
if (field_value === undefined || field_value === null) {
|
|
445
|
+
if (!index_metadata.sparse) {
|
|
446
|
+
const index_key = build_index_key(database_name, collection_name, index_metadata.field, null);
|
|
447
|
+
const existing_ids = index_db.get(index_key) || [];
|
|
448
|
+
existing_ids.push(document._id);
|
|
449
|
+
index_db.put(index_key, existing_ids);
|
|
450
|
+
}
|
|
451
|
+
continue;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
if (index_metadata.unique) {
|
|
455
|
+
const index_key = build_index_key(database_name, collection_name, index_metadata.field, field_value);
|
|
456
|
+
const existing_ids = index_db.get(index_key) || [];
|
|
457
|
+
|
|
458
|
+
if (existing_ids.length > 0) {
|
|
459
|
+
throw new Error(`Duplicate value for unique index on ${database_name}.${collection_name}.${index_metadata.field}: ${field_value}`);
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
const index_key = build_index_key(database_name, collection_name, index_metadata.field, field_value);
|
|
464
|
+
const existing_ids = index_db.get(index_key) || [];
|
|
465
|
+
existing_ids.push(document._id);
|
|
466
|
+
index_db.put(index_key, existing_ids);
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
} catch (error) {
|
|
470
|
+
log.error('Failed to update indexes on insert', {
|
|
471
|
+
database: database_name,
|
|
472
|
+
collection: collection_name,
|
|
473
|
+
document_id: document._id,
|
|
474
|
+
error: error.message
|
|
475
|
+
});
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
478
|
+
};
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Updates all relevant indexes when a document is modified.
|
|
482
|
+
* @param {string} database_name - Name of the database
|
|
483
|
+
* @param {string} collection_name - Name of the collection
|
|
484
|
+
* @param {Object} old_document - Document before update
|
|
485
|
+
* @param {Object} new_document - Document after update
|
|
486
|
+
* @returns {Promise<void>} Promise that resolves when indexes are updated
|
|
487
|
+
* @throws {Error} When unique constraint violations occur or update fails
|
|
488
|
+
*/
|
|
489
|
+
const update_indexes_on_update = async (database_name, collection_name, old_document, new_document) => {
|
|
490
|
+
const log = create_context_logger();
|
|
491
|
+
const index_db = get_index_database();
|
|
492
|
+
|
|
493
|
+
try {
|
|
494
|
+
const indexes = get_indexes(database_name, collection_name);
|
|
495
|
+
|
|
496
|
+
await index_db.transaction(() => {
|
|
497
|
+
for (const index_metadata of indexes) {
|
|
498
|
+
const old_value = get_field_value(old_document, index_metadata.field);
|
|
499
|
+
const new_value = get_field_value(new_document, index_metadata.field);
|
|
500
|
+
|
|
501
|
+
if (old_value === new_value) {
|
|
502
|
+
continue;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
if (old_value !== undefined && old_value !== null) {
|
|
506
|
+
const old_index_key = build_index_key(database_name, collection_name, index_metadata.field, old_value);
|
|
507
|
+
const old_ids = index_db.get(old_index_key) || [];
|
|
508
|
+
const filtered_ids = old_ids.filter(id => id !== old_document._id);
|
|
509
|
+
|
|
510
|
+
if (filtered_ids.length === 0) {
|
|
511
|
+
index_db.remove(old_index_key);
|
|
512
|
+
} else {
|
|
513
|
+
index_db.put(old_index_key, filtered_ids);
|
|
514
|
+
}
|
|
515
|
+
} else if (!index_metadata.sparse) {
|
|
516
|
+
const old_index_key = build_index_key(database_name, collection_name, index_metadata.field, null);
|
|
517
|
+
const old_ids = index_db.get(old_index_key) || [];
|
|
518
|
+
const filtered_ids = old_ids.filter(id => id !== old_document._id);
|
|
519
|
+
|
|
520
|
+
if (filtered_ids.length === 0) {
|
|
521
|
+
index_db.remove(old_index_key);
|
|
522
|
+
} else {
|
|
523
|
+
index_db.put(old_index_key, filtered_ids);
|
|
524
|
+
}
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (new_value !== undefined && new_value !== null) {
|
|
528
|
+
if (index_metadata.unique) {
|
|
529
|
+
const new_index_key = build_index_key(database_name, collection_name, index_metadata.field, new_value);
|
|
530
|
+
const existing_ids = index_db.get(new_index_key) || [];
|
|
531
|
+
|
|
532
|
+
if (existing_ids.length > 0) {
|
|
533
|
+
throw new Error(`Duplicate value for unique index on ${database_name}.${collection_name}.${index_metadata.field}: ${new_value}`);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
const new_index_key = build_index_key(database_name, collection_name, index_metadata.field, new_value);
|
|
538
|
+
const existing_ids = index_db.get(new_index_key) || [];
|
|
539
|
+
existing_ids.push(new_document._id);
|
|
540
|
+
index_db.put(new_index_key, existing_ids);
|
|
541
|
+
} else if (!index_metadata.sparse) {
|
|
542
|
+
const new_index_key = build_index_key(database_name, collection_name, index_metadata.field, null);
|
|
543
|
+
const existing_ids = index_db.get(new_index_key) || [];
|
|
544
|
+
existing_ids.push(new_document._id);
|
|
545
|
+
index_db.put(new_index_key, existing_ids);
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
});
|
|
549
|
+
} catch (error) {
|
|
550
|
+
log.error('Failed to update indexes on update', {
|
|
551
|
+
database: database_name,
|
|
552
|
+
collection: collection_name,
|
|
553
|
+
document_id: old_document._id,
|
|
554
|
+
error: error.message
|
|
555
|
+
});
|
|
556
|
+
throw error;
|
|
557
|
+
}
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
/**
|
|
561
|
+
* Updates all relevant indexes when a document is deleted.
|
|
562
|
+
* @param {string} database_name - Name of the database
|
|
563
|
+
* @param {string} collection_name - Name of the collection
|
|
564
|
+
* @param {Object} document - Document being deleted
|
|
565
|
+
* @returns {Promise<void>} Promise that resolves when indexes are updated
|
|
566
|
+
* @throws {Error} When index update fails
|
|
567
|
+
*/
|
|
568
|
+
const update_indexes_on_delete = async (database_name, collection_name, document) => {
|
|
569
|
+
const log = create_context_logger();
|
|
570
|
+
const index_db = get_index_database();
|
|
571
|
+
|
|
572
|
+
try {
|
|
573
|
+
const indexes = get_indexes(database_name, collection_name);
|
|
574
|
+
|
|
575
|
+
await index_db.transaction(() => {
|
|
576
|
+
for (const index_metadata of indexes) {
|
|
577
|
+
const field_value = get_field_value(document, index_metadata.field);
|
|
578
|
+
|
|
579
|
+
let index_key;
|
|
580
|
+
if (field_value === undefined || field_value === null) {
|
|
581
|
+
if (!index_metadata.sparse) {
|
|
582
|
+
index_key = build_index_key(database_name, collection_name, index_metadata.field, null);
|
|
583
|
+
} else {
|
|
584
|
+
continue;
|
|
585
|
+
}
|
|
586
|
+
} else {
|
|
587
|
+
index_key = build_index_key(database_name, collection_name, index_metadata.field, field_value);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
const existing_ids = index_db.get(index_key) || [];
|
|
591
|
+
const filtered_ids = existing_ids.filter(id => id !== document._id);
|
|
592
|
+
|
|
593
|
+
if (filtered_ids.length === 0) {
|
|
594
|
+
index_db.remove(index_key);
|
|
595
|
+
} else {
|
|
596
|
+
index_db.put(index_key, filtered_ids);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
});
|
|
600
|
+
} catch (error) {
|
|
601
|
+
log.error('Failed to update indexes on delete', {
|
|
602
|
+
database: database_name,
|
|
603
|
+
collection: collection_name,
|
|
604
|
+
document_id: document._id,
|
|
605
|
+
error: error.message
|
|
606
|
+
});
|
|
607
|
+
throw error;
|
|
608
|
+
}
|
|
609
|
+
};
|
|
610
|
+
|
|
611
|
+
/**
|
|
612
|
+
* Finds document IDs using an index for the specified field and operator.
|
|
613
|
+
* @param {string} database_name - Name of the database
|
|
614
|
+
* @param {string} collection_name - Name of the collection
|
|
615
|
+
* @param {string} field_name - Name of the indexed field
|
|
616
|
+
* @param {string} operator - Query operator ($eq, $in, $exists, or 'eq')
|
|
617
|
+
* @param {*} value - Value to search for
|
|
618
|
+
* @returns {Array<string>|null} Array of document IDs or null if no matches/unsupported operator
|
|
619
|
+
* @throws {Error} When $in operator used with non-array value
|
|
620
|
+
*/
|
|
621
|
+
const find_documents_by_index = (database_name, collection_name, field_name, operator, value) => {
|
|
622
|
+
const log = create_context_logger();
|
|
623
|
+
const index_db = get_index_database();
|
|
624
|
+
const document_ids = new Set();
|
|
625
|
+
|
|
626
|
+
try {
|
|
627
|
+
switch (operator) {
|
|
628
|
+
case '$eq':
|
|
629
|
+
case 'eq': {
|
|
630
|
+
const index_key = build_index_key(database_name, collection_name, field_name, value);
|
|
631
|
+
const ids = index_db.get(index_key) || [];
|
|
632
|
+
ids.forEach(id => document_ids.add(id));
|
|
633
|
+
break;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
case '$in': {
|
|
637
|
+
if (!Array.isArray(value)) {
|
|
638
|
+
throw new Error('$in operator requires an array value');
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
for (const val of value) {
|
|
642
|
+
const index_key = build_index_key(database_name, collection_name, field_name, val);
|
|
643
|
+
const ids = index_db.get(index_key) || [];
|
|
644
|
+
ids.forEach(id => document_ids.add(id));
|
|
645
|
+
}
|
|
646
|
+
break;
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
case '$exists': {
|
|
650
|
+
const index_prefix = `index:${database_name}:${collection_name}:${field_name}:`;
|
|
651
|
+
const range = index_db.getRange({ start: index_prefix, end: index_prefix + '\xFF' });
|
|
652
|
+
|
|
653
|
+
for (const { key, value: ids } of range) {
|
|
654
|
+
const parsed = parse_index_key(key);
|
|
655
|
+
const has_value = parsed && parsed.value !== 'null';
|
|
656
|
+
|
|
657
|
+
if ((value && has_value) || (!value && !has_value)) {
|
|
658
|
+
ids.forEach(id => document_ids.add(id));
|
|
659
|
+
}
|
|
660
|
+
}
|
|
661
|
+
break;
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
default:
|
|
665
|
+
return null;
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
const result = Array.from(document_ids);
|
|
669
|
+
|
|
670
|
+
log.info('Index query completed', {
|
|
671
|
+
database: database_name,
|
|
672
|
+
collection: collection_name,
|
|
673
|
+
field: field_name,
|
|
674
|
+
operator,
|
|
675
|
+
document_ids_found: result.length
|
|
676
|
+
});
|
|
677
|
+
|
|
678
|
+
return result.length > 0 ? result : null;
|
|
679
|
+
} catch (error) {
|
|
680
|
+
log.error('Failed to query index', {
|
|
681
|
+
database: database_name,
|
|
682
|
+
collection: collection_name,
|
|
683
|
+
field: field_name,
|
|
684
|
+
operator,
|
|
685
|
+
error: error.message
|
|
686
|
+
});
|
|
687
|
+
return null;
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
/**
|
|
692
|
+
* Determines if an index can be used for the given filter and returns optimization info.
|
|
693
|
+
* @param {string} database_name - Name of the database
|
|
694
|
+
* @param {string} collection_name - Name of the collection
|
|
695
|
+
* @param {Object} filter - Query filter object
|
|
696
|
+
* @returns {Object|null} Index usage info or null if no suitable index found
|
|
697
|
+
* @returns {string} returns.field - Field name that can use index
|
|
698
|
+
* @returns {Array<string>} returns.operators - Supported operators for the field
|
|
699
|
+
*/
|
|
700
|
+
const can_use_index = (database_name, collection_name, filter) => {
|
|
701
|
+
const indexes = get_indexes(database_name, collection_name);
|
|
702
|
+
|
|
703
|
+
for (const [field, value] of Object.entries(filter)) {
|
|
704
|
+
const has_index = indexes.some(index => index.field === field);
|
|
705
|
+
|
|
706
|
+
if (has_index) {
|
|
707
|
+
if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
|
|
708
|
+
const operators = Object.keys(value);
|
|
709
|
+
const supported_operators = ['$eq', '$in', '$exists'];
|
|
710
|
+
|
|
711
|
+
if (operators.some(op => supported_operators.includes(op))) {
|
|
712
|
+
return { field, operators: operators.filter(op => supported_operators.includes(op)) };
|
|
713
|
+
}
|
|
714
|
+
} else {
|
|
715
|
+
return { field, operators: ['eq'] };
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
return null;
|
|
721
|
+
};
|
|
722
|
+
|
|
723
|
+
/**
|
|
724
|
+
* Cleans up the index database by removing all entries and resetting the reference.
|
|
725
|
+
* Used during database cleanup and shutdown procedures.
|
|
726
|
+
*/
|
|
727
|
+
const cleanup_index_database = () => {
|
|
728
|
+
if (index_db) {
|
|
729
|
+
try {
|
|
730
|
+
// Clear all index metadata and index entries
|
|
731
|
+
const range = index_db.getRange();
|
|
732
|
+
for (const { key } of range) {
|
|
733
|
+
index_db.remove(key);
|
|
734
|
+
}
|
|
735
|
+
} catch (error) {
|
|
736
|
+
// Ignore errors during cleanup
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
index_db = null;
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
export {
|
|
743
|
+
initialize_index_database,
|
|
744
|
+
get_index_database,
|
|
745
|
+
create_index,
|
|
746
|
+
drop_index,
|
|
747
|
+
get_indexes,
|
|
748
|
+
update_indexes_on_insert,
|
|
749
|
+
update_indexes_on_update,
|
|
750
|
+
update_indexes_on_delete,
|
|
751
|
+
find_documents_by_index,
|
|
752
|
+
can_use_index,
|
|
753
|
+
build_index_key,
|
|
754
|
+
parse_index_key,
|
|
755
|
+
cleanup_index_database
|
|
756
|
+
};
|