@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.
Files changed (354) hide show
  1. package/.build/getFilesToBuild.js +26 -0
  2. package/.build/getPlatformSafeFilePath.js +6 -0
  3. package/.build/getPlatformSafePath.js +6 -0
  4. package/.build/index.js +88 -0
  5. package/.build/isWindows.js +3 -0
  6. package/API_KEY +1 -0
  7. package/README.md +1821 -0
  8. package/data/data.mdb +0 -0
  9. package/data/lock.mdb +0 -0
  10. package/dist/client/database.js +1 -0
  11. package/dist/client/index.js +1 -0
  12. package/dist/server/cluster/index.js +1 -0
  13. package/dist/server/cluster/master.js +20 -0
  14. package/dist/server/cluster/worker.js +1 -0
  15. package/dist/server/index.js +1 -0
  16. package/dist/server/lib/api_key_manager.js +9 -0
  17. package/dist/server/lib/auth_manager.js +1 -0
  18. package/dist/server/lib/auto_index_manager.js +1 -0
  19. package/dist/server/lib/backup_manager.js +1 -0
  20. package/dist/server/lib/connection_manager.js +1 -0
  21. package/dist/server/lib/disk_utils.js +2 -0
  22. package/dist/server/lib/http_server.js +405 -0
  23. package/dist/server/lib/index_manager.js +1 -0
  24. package/dist/server/lib/load_settings.js +1 -0
  25. package/dist/server/lib/logger.js +1 -0
  26. package/dist/server/lib/op_types.js +1 -0
  27. package/dist/server/lib/operation_dispatcher.js +1 -0
  28. package/dist/server/lib/operations/admin.js +1 -0
  29. package/dist/server/lib/operations/bulk_write.js +1 -0
  30. package/dist/server/lib/operations/create_index.js +1 -0
  31. package/dist/server/lib/operations/delete_one.js +1 -0
  32. package/dist/server/lib/operations/drop_index.js +1 -0
  33. package/dist/server/lib/operations/find.js +1 -0
  34. package/dist/server/lib/operations/find_one.js +1 -0
  35. package/dist/server/lib/operations/get_indexes.js +1 -0
  36. package/dist/server/lib/operations/insert_one.js +1 -0
  37. package/dist/server/lib/operations/update_one.js +1 -0
  38. package/dist/server/lib/performance_monitor.js +1 -0
  39. package/dist/server/lib/query_engine.js +1 -0
  40. package/dist/server/lib/recovery_manager.js +1 -0
  41. package/dist/server/lib/replication_manager.js +1 -0
  42. package/dist/server/lib/safe_json_parse.js +1 -0
  43. package/dist/server/lib/send_response.js +1 -0
  44. package/dist/server/lib/tcp_protocol.js +1 -0
  45. package/dist/server/lib/write_forwarder.js +1 -0
  46. package/dist/server/lib/write_queue.js +1 -0
  47. package/increment_version.js +3 -0
  48. package/logs/.013e15b54597d05db4b4b53ecc37b10c92a72927-audit.json +20 -0
  49. package/logs/.02de550a67ea0f5961faa2dfd458a4d06f59ebd1-audit.json +20 -0
  50. package/logs/.03494ba24eb3c72214b4068a77d54b8993bee651-audit.json +20 -0
  51. package/logs/.06309ec60b339be1259a7993dd09c732f8907fbc-audit.json +20 -0
  52. package/logs/.0663a04dcfa17285661e5e1b8cfa51f41523b210-audit.json +20 -0
  53. package/logs/.0f06e6c4c9b824622729e13927587479e5060391-audit.json +20 -0
  54. package/logs/.16ccf58682ecb22b3e3ec63f0da1b7fe9be56528-audit.json +20 -0
  55. package/logs/.1fa1a5d02f496474b1ab473524c65c984146a9ad-audit.json +20 -0
  56. package/logs/.2223c0ae3bea6f0d62c62b1d319cc8634856abb7-audit.json +20 -0
  57. package/logs/.23dc79ffda3e083665e6f5993f59397adcbf4a46-audit.json +20 -0
  58. package/logs/.28104f49b03906b189eefd1cd462cb46c3c0af22-audit.json +20 -0
  59. package/logs/.29cdbf13808abe6a0ce70ee2f2efdd680ce3fd8e-audit.json +20 -0
  60. package/logs/.2a9889afd071f77f41f5170d08703a0afca866b7-audit.json +20 -0
  61. package/logs/.2acec3d1940a2bbed487528b703ee5948959a599-audit.json +20 -0
  62. package/logs/.2fb60ff326338c02bfedbcd0e936444e4a216750-audit.json +20 -0
  63. package/logs/.318fc7a19530d76a345f030f7cad00dda15300e7-audit.json +20 -0
  64. package/logs/.3cf27043e19085f908cedc7701e6d013463208ee-audit.json +25 -0
  65. package/logs/.3d90d785415817fc443402843b7c95f8371adc9b-audit.json +20 -0
  66. package/logs/.4074bca620375f72966fc52dfd439577727671e5-audit.json +20 -0
  67. package/logs/.40eecf018417ea80a70ea8ec7a3cc9406bc6334b-audit.json +20 -0
  68. package/logs/.50e974f1ef7c365fca6a1251b2e2c2252914cb5e-audit.json +20 -0
  69. package/logs/.52cb7d9e4223cf26ba36006ac26b949a97c7923c-audit.json +20 -0
  70. package/logs/.54befcdb84c15aad980705a31bcc9f555c3577ab-audit.json +20 -0
  71. package/logs/.57dfb70e22eddb84db2e3c0ceeefac5c0b9baffa-audit.json +20 -0
  72. package/logs/.5f0b24705a1eaad4eca4968f2d86f91b3f9be683-audit.json +20 -0
  73. package/logs/.61ba98fdda7db58576b382fee07904e5db1169d6-audit.json +20 -0
  74. package/logs/.6235017727ef6b199d569a99d6aa8c8e80a1b475-audit.json +20 -0
  75. package/logs/.63db16193699219489d218a1ddea5dde3750cae4-audit.json +20 -0
  76. package/logs/.64fb67dfe14149c9eef728d79bf30a54da809c60-audit.json +20 -0
  77. package/logs/.669137453368987c1f311b5345342527afb54e50-audit.json +20 -0
  78. package/logs/.7a71f8c89ea28ae266d356aeff6306e876a30fbb-audit.json +20 -0
  79. package/logs/.7afbaa90fe9dc3a7d682676f9bb79f9a1b1fd9a6-audit.json +20 -0
  80. package/logs/.7ca29e322cd05327035de850099e7610864f2347-audit.json +20 -0
  81. package/logs/.83335ab3347e449dae03455a110aaf7f120d4802-audit.json +20 -0
  82. package/logs/.8c2487b5fd445d2c8e5c483c80b9fa99bbf1ca58-audit.json +20 -0
  83. package/logs/.8c8b9dc386922c9f3b4c13251af7052aac1d24c0-audit.json +20 -0
  84. package/logs/.8d6155d94640c4863301ae0fee5e4e7372a21446-audit.json +20 -0
  85. package/logs/.944a3119a243deea7c8270d5d9e582bb1d0eaa10-audit.json +20 -0
  86. package/logs/.9816a845c30fb2909f3b26a23eeb3538ebcad5db-audit.json +20 -0
  87. package/logs/.9dc08784e38b865488177c26d4af5934555e0323-audit.json +20 -0
  88. package/logs/.9dd27d2e0e454ac0a37600206d1cac5493b0d7ee-audit.json +20 -0
  89. package/logs/.a3d486feeac7654c59b547de96600e8849a06d4f-audit.json +20 -0
  90. package/logs/.a5b811f4def22250f86cc18870d7c4573625df22-audit.json +20 -0
  91. package/logs/.a61648eb5f830e0b6f508ac35e4f8f629d2ad4c7-audit.json +20 -0
  92. package/logs/.a89016d507045771b4b5a65656944a9c0f1e528b-audit.json +20 -0
  93. package/logs/.a99bee160a1c590be959af46bacc02724803f691-audit.json +20 -0
  94. package/logs/.ada7906d6243fd7da802f03d86c4ae5dd9df6236-audit.json +20 -0
  95. package/logs/.b518339ee942143b6af983af167f5bbb6983b4de-audit.json +20 -0
  96. package/logs/.b51b124b166d53c9519017856ea610d61d65fabe-audit.json +20 -0
  97. package/logs/.b7a6aee19f58e55633d5e4a3709041c47dfff975-audit.json +20 -0
  98. package/logs/.bd7a8a6ba9c55d557a4867ab53f02e3ec2e1553d-audit.json +20 -0
  99. package/logs/.c1435dafe453b169d6392b25065f3cf4ab6fbb21-audit.json +20 -0
  100. package/logs/.c17e1ce043109f77dc2f0e2aa290a9d1ed842c03-audit.json +20 -0
  101. package/logs/.ca62637ce9540e5a38a2fbedb2115febb6ad308a-audit.json +15 -0
  102. package/logs/.ccee67b9c176967f8977071409a41f5cb5cd6ad4-audit.json +20 -0
  103. package/logs/.db24043417ea79a6f14cd947476399e53930b48d-audit.json +20 -0
  104. package/logs/.e0f12acccb57829f5f33712bb2e2607ecd808147-audit.json +20 -0
  105. package/logs/.e9b6cc33d0bbd2e644c4e2bf44d177f850016557-audit.json +20 -0
  106. package/logs/.f15291d434808e3bdca7963ccd2e73893be027e6-audit.json +20 -0
  107. package/logs/.f4bdf9e21ef84f8a3fae3ffb32bbc39275991351-audit.json +15 -0
  108. package/logs/.fbac3aefac1e81b4230df5aa50667cb90d51024f-audit.json +20 -0
  109. package/logs/.fcfd495c0a9169db243f4a4f21878ee02b76413c-audit.json +20 -0
  110. package/logs/admin-2025-09-12.log +580 -0
  111. package/logs/admin-2025-09-15.log +283 -0
  112. package/logs/admin-error-2025-09-12.log +22 -0
  113. package/logs/admin-error-2025-09-15.log +10 -0
  114. package/logs/api_key_manager-2025-09-12.log +658 -0
  115. package/logs/api_key_manager-2025-09-15.log +295 -0
  116. package/logs/api_key_manager-error-2025-09-12.log +0 -0
  117. package/logs/api_key_manager-error-2025-09-15.log +0 -0
  118. package/logs/auth_manager-2025-09-12.log +4432 -0
  119. package/logs/auth_manager-2025-09-15.log +2000 -0
  120. package/logs/auth_manager-error-2025-09-12.log +11 -0
  121. package/logs/auth_manager-error-2025-09-15.log +5 -0
  122. package/logs/auto_index_manager-2025-09-12.log +84 -0
  123. package/logs/auto_index_manager-2025-09-15.log +45 -0
  124. package/logs/auto_index_manager-error-2025-09-12.log +6 -0
  125. package/logs/auto_index_manager-error-2025-09-15.log +0 -0
  126. package/logs/backup_manager-2025-09-12.log +198 -0
  127. package/logs/backup_manager-2025-09-15.log +90 -0
  128. package/logs/backup_manager-error-2025-09-12.log +198 -0
  129. package/logs/backup_manager-error-2025-09-15.log +90 -0
  130. package/logs/bulk_write-2025-09-12.log +66 -0
  131. package/logs/bulk_write-2025-09-15.log +38 -0
  132. package/logs/bulk_write-error-2025-09-12.log +0 -0
  133. package/logs/bulk_write-error-2025-09-15.log +0 -0
  134. package/logs/connection_manager-2025-09-12.log +2412 -0
  135. package/logs/connection_manager-2025-09-15.log +1132 -0
  136. package/logs/connection_manager-error-2025-09-12.log +0 -0
  137. package/logs/connection_manager-error-2025-09-15.log +0 -0
  138. package/logs/create_index-2025-09-12.log +302 -0
  139. package/logs/create_index-2025-09-15.log +158 -0
  140. package/logs/create_index-error-2025-09-12.log +30 -0
  141. package/logs/create_index-error-2025-09-15.log +13 -0
  142. package/logs/delete_one-2025-09-12.log +73 -0
  143. package/logs/delete_one-2025-09-15.log +43 -0
  144. package/logs/delete_one-error-2025-09-12.log +0 -0
  145. package/logs/delete_one-error-2025-09-15.log +0 -0
  146. package/logs/disk_utils-2025-09-12.log +4954 -0
  147. package/logs/disk_utils-2025-09-15.log +2446 -0
  148. package/logs/disk_utils-error-2025-09-12.log +0 -0
  149. package/logs/disk_utils-error-2025-09-15.log +0 -0
  150. package/logs/drop_index-2025-09-12.log +41 -0
  151. package/logs/drop_index-2025-09-15.log +23 -0
  152. package/logs/drop_index-error-2025-09-12.log +11 -0
  153. package/logs/drop_index-error-2025-09-15.log +5 -0
  154. package/logs/find-2025-09-12.log +1050 -0
  155. package/logs/find-2025-09-15.log +592 -0
  156. package/logs/find-error-2025-09-12.log +1 -0
  157. package/logs/find-error-2025-09-15.log +0 -0
  158. package/logs/find_one-2025-09-12.log +425 -0
  159. package/logs/find_one-2025-09-15.log +264 -0
  160. package/logs/find_one-error-2025-09-12.log +5 -0
  161. package/logs/find_one-error-2025-09-15.log +0 -0
  162. package/logs/get_indexes-2025-09-12.log +84 -0
  163. package/logs/get_indexes-2025-09-15.log +56 -0
  164. package/logs/get_indexes-error-2025-09-12.log +6 -0
  165. package/logs/get_indexes-error-2025-09-15.log +0 -0
  166. package/logs/http_server-2025-09-12.log +2772 -0
  167. package/logs/http_server-2025-09-15.log +1276 -0
  168. package/logs/http_server-error-2025-09-12.log +212 -0
  169. package/logs/http_server-error-2025-09-15.log +44 -0
  170. package/logs/index_manager-2025-09-12.log +5031 -0
  171. package/logs/index_manager-2025-09-15.log +2909 -0
  172. package/logs/index_manager-error-2025-09-12.log +80 -0
  173. package/logs/index_manager-error-2025-09-15.log +38 -0
  174. package/logs/insert_one-2025-09-12.log +2181 -0
  175. package/logs/insert_one-2025-09-15.log +1293 -0
  176. package/logs/insert_one-error-2025-09-12.log +0 -0
  177. package/logs/insert_one-error-2025-09-15.log +0 -0
  178. package/logs/master-2025-09-12.log +1882 -0
  179. package/logs/master-2025-09-15.log +910 -0
  180. package/logs/master-error-2025-09-12.log +80 -0
  181. package/logs/master-error-2025-09-15.log +0 -0
  182. package/logs/operation_dispatcher-2025-09-12.log +751 -0
  183. package/logs/operation_dispatcher-2025-09-15.log +359 -0
  184. package/logs/operation_dispatcher-error-2025-09-12.log +33 -0
  185. package/logs/operation_dispatcher-error-2025-09-15.log +11 -0
  186. package/logs/performance_monitor-2025-09-12.log +14889 -0
  187. package/logs/performance_monitor-2025-09-15.log +6803 -0
  188. package/logs/performance_monitor-error-2025-09-12.log +0 -0
  189. package/logs/performance_monitor-error-2025-09-15.log +0 -0
  190. package/logs/query_engine-2025-09-12.log +5310 -0
  191. package/logs/query_engine-2025-09-15.log +2639 -0
  192. package/logs/query_engine-error-2025-09-12.log +0 -0
  193. package/logs/query_engine-error-2025-09-15.log +0 -0
  194. package/logs/recovery_manager-2025-09-12.log +462 -0
  195. package/logs/recovery_manager-2025-09-15.log +210 -0
  196. package/logs/recovery_manager-error-2025-09-12.log +22 -0
  197. package/logs/recovery_manager-error-2025-09-15.log +10 -0
  198. package/logs/replication-2025-09-12.log +1923 -0
  199. package/logs/replication-2025-09-15.log +917 -0
  200. package/logs/replication-error-2025-09-12.log +33 -0
  201. package/logs/replication-error-2025-09-15.log +15 -0
  202. package/logs/server-2025-09-12.log +2601 -0
  203. package/logs/server-2025-09-15.log +1191 -0
  204. package/logs/server-error-2025-09-12.log +0 -0
  205. package/logs/server-error-2025-09-15.log +0 -0
  206. package/logs/tcp_protocol-2025-09-12.log +22 -0
  207. package/logs/tcp_protocol-2025-09-15.log +10 -0
  208. package/logs/tcp_protocol-error-2025-09-12.log +22 -0
  209. package/logs/tcp_protocol-error-2025-09-15.log +10 -0
  210. package/logs/test-2025-09-12.log +0 -0
  211. package/logs/test-2025-09-15.log +0 -0
  212. package/logs/test-error-2025-09-12.log +0 -0
  213. package/logs/test-error-2025-09-15.log +0 -0
  214. package/logs/update_one-2025-09-12.log +173 -0
  215. package/logs/update_one-2025-09-15.log +118 -0
  216. package/logs/update_one-error-2025-09-12.log +0 -0
  217. package/logs/update_one-error-2025-09-15.log +0 -0
  218. package/logs/worker-2025-09-12.log +1457 -0
  219. package/logs/worker-2025-09-15.log +695 -0
  220. package/logs/worker-error-2025-09-12.log +0 -0
  221. package/logs/worker-error-2025-09-15.log +0 -0
  222. package/logs/write_forwarder-2025-09-12.log +1956 -0
  223. package/logs/write_forwarder-2025-09-15.log +932 -0
  224. package/logs/write_forwarder-error-2025-09-12.log +66 -0
  225. package/logs/write_forwarder-error-2025-09-15.log +30 -0
  226. package/logs/write_queue-2025-09-12.log +612 -0
  227. package/logs/write_queue-2025-09-15.log +301 -0
  228. package/logs/write_queue-error-2025-09-12.log +184 -0
  229. package/logs/write_queue-error-2025-09-15.log +83 -0
  230. package/package.json +48 -0
  231. package/prompts/01-core-infrastructure.md +56 -0
  232. package/prompts/02-secondary-indexing.md +65 -0
  233. package/prompts/03-write-serialization.md +63 -0
  234. package/prompts/04-enhanced-authentication.md +75 -0
  235. package/prompts/05-comprehensive-admin-operations.md +75 -0
  236. package/prompts/06-backup-and-restore-system.md +106 -0
  237. package/prompts/07-production-safety-features.md +107 -0
  238. package/prompts/08-tcp-client-library.md +121 -0
  239. package/prompts/09-api-method-chaining.md +134 -0
  240. package/prompts/10-automatic-index-creation.md +223 -0
  241. package/prompts/11-operation-naming-consistency.md +268 -0
  242. package/prompts/12-tcp-replication-system.md +333 -0
  243. package/prompts/13-master-read-write-operations.md +57 -0
  244. package/prompts/14-index-upsert-operations.md +68 -0
  245. package/prompts/15-client-api-return-types.md +81 -0
  246. package/prompts/16-server-setup-ui.md +97 -0
  247. package/prompts/17-emergency-password-change.md +108 -0
  248. package/prompts/18-joystick-framework-integration.md +116 -0
  249. package/prompts/19-api-key-authentication-system.md +137 -0
  250. package/prompts/20-configurable-server-port.md +105 -0
  251. package/prompts/21-multi-database-support.md +161 -0
  252. package/prompts/FULL_TEXT_SEARCH.md +293 -0
  253. package/prompts/PROMPTS.md +158 -0
  254. package/prompts/README.md +221 -0
  255. package/prompts/TYPESCRIPT_GENERATION.md +179 -0
  256. package/src/client/database.js +166 -0
  257. package/src/client/index.js +752 -0
  258. package/src/server/cluster/index.js +53 -0
  259. package/src/server/cluster/master.js +774 -0
  260. package/src/server/cluster/worker.js +537 -0
  261. package/src/server/index.js +540 -0
  262. package/src/server/lib/api_key_manager.js +473 -0
  263. package/src/server/lib/auth_manager.js +375 -0
  264. package/src/server/lib/auto_index_manager.js +681 -0
  265. package/src/server/lib/backup_manager.js +650 -0
  266. package/src/server/lib/connection_manager.js +218 -0
  267. package/src/server/lib/disk_utils.js +118 -0
  268. package/src/server/lib/http_server.js +1165 -0
  269. package/src/server/lib/index_manager.js +756 -0
  270. package/src/server/lib/load_settings.js +143 -0
  271. package/src/server/lib/logger.js +135 -0
  272. package/src/server/lib/op_types.js +29 -0
  273. package/src/server/lib/operation_dispatcher.js +268 -0
  274. package/src/server/lib/operations/admin.js +808 -0
  275. package/src/server/lib/operations/bulk_write.js +367 -0
  276. package/src/server/lib/operations/create_index.js +68 -0
  277. package/src/server/lib/operations/delete_one.js +114 -0
  278. package/src/server/lib/operations/drop_index.js +58 -0
  279. package/src/server/lib/operations/find.js +340 -0
  280. package/src/server/lib/operations/find_one.js +319 -0
  281. package/src/server/lib/operations/get_indexes.js +52 -0
  282. package/src/server/lib/operations/insert_one.js +113 -0
  283. package/src/server/lib/operations/update_one.js +225 -0
  284. package/src/server/lib/performance_monitor.js +313 -0
  285. package/src/server/lib/query_engine.js +243 -0
  286. package/src/server/lib/recovery_manager.js +388 -0
  287. package/src/server/lib/replication_manager.js +727 -0
  288. package/src/server/lib/safe_json_parse.js +21 -0
  289. package/src/server/lib/send_response.js +47 -0
  290. package/src/server/lib/tcp_protocol.js +130 -0
  291. package/src/server/lib/write_forwarder.js +636 -0
  292. package/src/server/lib/write_queue.js +335 -0
  293. package/test_data/data.mdb +0 -0
  294. package/test_data/lock.mdb +0 -0
  295. package/tests/client/index.test.js +1232 -0
  296. package/tests/server/cluster/cluster.test.js +248 -0
  297. package/tests/server/cluster/master_read_write_operations.test.js +577 -0
  298. package/tests/server/index.test.js +651 -0
  299. package/tests/server/integration/authentication_integration.test.js +294 -0
  300. package/tests/server/integration/auto_indexing_integration.test.js +268 -0
  301. package/tests/server/integration/backup_integration.test.js +513 -0
  302. package/tests/server/integration/indexing_integration.test.js +126 -0
  303. package/tests/server/integration/production_safety_integration.test.js +358 -0
  304. package/tests/server/integration/replication_integration.test.js +227 -0
  305. package/tests/server/integration/write_serialization_integration.test.js +246 -0
  306. package/tests/server/lib/api_key_manager.test.js +516 -0
  307. package/tests/server/lib/auth_manager.test.js +317 -0
  308. package/tests/server/lib/auto_index_manager.test.js +275 -0
  309. package/tests/server/lib/backup_manager.test.js +238 -0
  310. package/tests/server/lib/connection_manager.test.js +221 -0
  311. package/tests/server/lib/disk_utils.test.js +63 -0
  312. package/tests/server/lib/http_server.test.js +389 -0
  313. package/tests/server/lib/index_manager.test.js +301 -0
  314. package/tests/server/lib/load_settings.test.js +107 -0
  315. package/tests/server/lib/load_settings_port_config.test.js +243 -0
  316. package/tests/server/lib/logger.test.js +282 -0
  317. package/tests/server/lib/operations/admin.test.js +638 -0
  318. package/tests/server/lib/operations/bulk_write.test.js +128 -0
  319. package/tests/server/lib/operations/create_index.test.js +138 -0
  320. package/tests/server/lib/operations/delete_one.test.js +52 -0
  321. package/tests/server/lib/operations/drop_index.test.js +72 -0
  322. package/tests/server/lib/operations/find.test.js +93 -0
  323. package/tests/server/lib/operations/find_one.test.js +91 -0
  324. package/tests/server/lib/operations/get_indexes.test.js +87 -0
  325. package/tests/server/lib/operations/insert_one.test.js +42 -0
  326. package/tests/server/lib/operations/update_one.test.js +89 -0
  327. package/tests/server/lib/performance_monitor.test.js +185 -0
  328. package/tests/server/lib/query_engine.test.js +46 -0
  329. package/tests/server/lib/recovery_manager.test.js +414 -0
  330. package/tests/server/lib/replication_manager.test.js +202 -0
  331. package/tests/server/lib/safe_json_parse.test.js +45 -0
  332. package/tests/server/lib/send_response.test.js +155 -0
  333. package/tests/server/lib/tcp_protocol.test.js +169 -0
  334. package/tests/server/lib/write_forwarder.test.js +258 -0
  335. package/tests/server/lib/write_queue.test.js +255 -0
  336. package/tsconfig.json +30 -0
  337. package/types/client/index.d.ts +447 -0
  338. package/types/server/cluster/index.d.ts +28 -0
  339. package/types/server/cluster/master.d.ts +115 -0
  340. package/types/server/cluster/worker.d.ts +1 -0
  341. package/types/server/lib/auth_manager.d.ts +13 -0
  342. package/types/server/lib/backup_manager.d.ts +43 -0
  343. package/types/server/lib/connection_manager.d.ts +15 -0
  344. package/types/server/lib/disk_utils.d.ts +3 -0
  345. package/types/server/lib/index_manager.d.ts +24 -0
  346. package/types/server/lib/load_settings.d.ts +4 -0
  347. package/types/server/lib/logger.d.ts +44 -0
  348. package/types/server/lib/op_types.d.ts +6 -0
  349. package/types/server/lib/performance_monitor.d.ts +68 -0
  350. package/types/server/lib/query_engine.d.ts +10 -0
  351. package/types/server/lib/safe_json_parse.d.ts +7 -0
  352. package/types/server/lib/send_response.d.ts +3 -0
  353. package/types/server/lib/tcp_protocol.d.ts +12 -0
  354. 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
+ };