@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,1165 @@
1
+ /**
2
+ * @fileoverview HTTP server for JoystickDB setup and emergency recovery operations.
3
+ * Provides web-based interfaces for initial database setup with authentication configuration
4
+ * and emergency password recovery. Includes rate limiting, token validation, and comprehensive
5
+ * HTML interfaces for secure database administration without requiring TCP client access.
6
+ */
7
+
8
+ import http from 'http';
9
+ import { URL } from 'url';
10
+ import crypto from 'crypto';
11
+ import create_logger from './logger.js';
12
+ import { setup_authentication, get_auth_stats } from './auth_manager.js';
13
+ import {
14
+ is_token_valid,
15
+ record_failed_recovery_attempt,
16
+ change_password,
17
+ } from './recovery_manager.js';
18
+ import {
19
+ validate_api_key,
20
+ create_user,
21
+ get_all_users,
22
+ get_user,
23
+ update_user,
24
+ delete_user,
25
+ check_admin_user_exists
26
+ } from './api_key_manager.js';
27
+
28
+ const { create_context_logger } = create_logger('http_server');
29
+ const log = create_context_logger();
30
+
31
+ /** @type {http.Server|null} HTTP server instance for setup and recovery operations */
32
+ let http_server = null;
33
+
34
+ /** @type {string|null} Secure token for setup authentication */
35
+ let setup_token = null;
36
+
37
+ /** @type {boolean} Flag indicating if initial setup has been completed */
38
+ let setup_completed = false;
39
+
40
+ /** @type {Map<string, Array<number>>} Rate limiting tracking by IP address */
41
+ let rate_limit_attempts = new Map();
42
+
43
+ /** @type {number} Rate limiting window in milliseconds (1 minute) */
44
+ const RATE_LIMIT_WINDOW = 60 * 1000;
45
+
46
+ /** @type {number} Maximum setup attempts per IP within rate limit window */
47
+ const MAX_SETUP_ATTEMPTS = 10;
48
+
49
+ /**
50
+ * Generates a secure random UUID token for setup authentication.
51
+ * Uses Node.js crypto.randomUUID() for cryptographically secure token generation.
52
+ * @returns {string} Secure UUID token for setup authentication
53
+ */
54
+ const generate_setup_token = () => {
55
+ return crypto.randomUUID();
56
+ };
57
+
58
+ /**
59
+ * Determines if initial database setup is required.
60
+ * Checks authentication configuration status to determine if setup interface should be available.
61
+ * @returns {boolean} True if setup is required, false if already configured
62
+ */
63
+ const is_setup_required = () => {
64
+ const auth_stats = get_auth_stats();
65
+ return !auth_stats.configured;
66
+ };
67
+
68
+ /**
69
+ * Checks if an IP address is currently rate limited for setup attempts.
70
+ * Filters out expired attempts and updates the tracking map with recent attempts only.
71
+ * @param {string} ip - IP address to check for rate limiting
72
+ * @returns {boolean} True if IP is rate limited, false otherwise
73
+ */
74
+ const is_rate_limited = (ip) => {
75
+ const now = Date.now();
76
+ const attempts = rate_limit_attempts.get(ip) || [];
77
+
78
+ const recent_attempts = attempts.filter(timestamp => now - timestamp < RATE_LIMIT_WINDOW);
79
+ rate_limit_attempts.set(ip, recent_attempts);
80
+
81
+ return recent_attempts.length >= MAX_SETUP_ATTEMPTS;
82
+ };
83
+
84
+ /**
85
+ * Records a setup attempt for rate limiting purposes.
86
+ * Adds current timestamp to the IP's attempt history for rate limiting calculations.
87
+ * @param {string} ip - IP address making the setup attempt
88
+ * @returns {void}
89
+ */
90
+ const record_setup_attempt = (ip) => {
91
+ const now = Date.now();
92
+ const attempts = rate_limit_attempts.get(ip) || [];
93
+ attempts.push(now);
94
+ rate_limit_attempts.set(ip, attempts);
95
+ };
96
+
97
+ /**
98
+ * Creates HTML content for the database setup interface.
99
+ * Generates a complete HTML page with setup form, instructions, and error display.
100
+ * @param {string} token - Setup authentication token to include in form
101
+ * @param {string|null} [error=null] - Error message to display, if any
102
+ * @returns {string} Complete HTML page content for setup interface
103
+ */
104
+ const create_setup_html = (token, error = null) => {
105
+ const error_html = error ? `<div style="color: red; margin-bottom: 20px; padding: 10px; border: 1px solid red; background: #ffe6e6;">${error}</div>` : '';
106
+
107
+ return `<!DOCTYPE html>
108
+ <html>
109
+ <head>
110
+ <title>JoystickDB Setup</title>
111
+ <meta charset="utf-8">
112
+ <style>
113
+ body {
114
+ font-family: Arial, sans-serif;
115
+ max-width: 600px;
116
+ margin: 50px auto;
117
+ padding: 20px;
118
+ line-height: 1.6;
119
+ }
120
+ .container {
121
+ border: 1px solid #ddd;
122
+ padding: 30px;
123
+ border-radius: 5px;
124
+ background: #f9f9f9;
125
+ }
126
+ .button {
127
+ background: #007bff;
128
+ color: white;
129
+ padding: 10px 20px;
130
+ border: none;
131
+ border-radius: 3px;
132
+ cursor: pointer;
133
+ font-size: 16px;
134
+ }
135
+ .button:hover {
136
+ background: #0056b3;
137
+ }
138
+ .code {
139
+ background: #f4f4f4;
140
+ padding: 2px 6px;
141
+ border-radius: 3px;
142
+ font-family: monospace;
143
+ }
144
+ </style>
145
+ </head>
146
+ <body>
147
+ <div class="container">
148
+ <h1>JoystickDB Setup</h1>
149
+ ${error_html}
150
+ <p>Your JoystickDB server needs to be configured with authentication before it can be used.</p>
151
+ <p>Click the button below to generate a secure password and complete the setup process.</p>
152
+
153
+ <form method="POST" action="/setup?token=${token}">
154
+ <button type="submit" class="button">Setup JoystickDB</button>
155
+ </form>
156
+
157
+ <hr style="margin: 30px 0;">
158
+
159
+ <h3>What happens during setup?</h3>
160
+ <ul>
161
+ <li>A secure random password will be generated</li>
162
+ <li>Authentication configuration will be saved to <span class="code">settings.db.json</span></li>
163
+ <li>You'll receive the password to use with your client connections</li>
164
+ </ul>
165
+
166
+ <h3>After setup:</h3>
167
+ <ul>
168
+ <li>Set the environment variable: <span class="code">JOYSTICKDB_PASSWORD=&lt;your_password&gt;</span></li>
169
+ <li>Connect your client to TCP port 1983</li>
170
+ <li>This setup interface will no longer be accessible</li>
171
+ </ul>
172
+ </div>
173
+ </body>
174
+ </html>`;
175
+ };
176
+
177
+ /**
178
+ * Creates HTML content for successful setup completion.
179
+ * Displays the generated password and provides instructions for client connection.
180
+ * @param {string} password - Generated password to display to user
181
+ * @returns {string} Complete HTML page content for setup success
182
+ */
183
+ const create_success_html = (password) => {
184
+ return `<!DOCTYPE html>
185
+ <html>
186
+ <head>
187
+ <title>JoystickDB Setup Complete</title>
188
+ <meta charset="utf-8">
189
+ <style>
190
+ body {
191
+ font-family: Arial, sans-serif;
192
+ max-width: 600px;
193
+ margin: 50px auto;
194
+ padding: 20px;
195
+ line-height: 1.6;
196
+ }
197
+ .container {
198
+ border: 1px solid #28a745;
199
+ padding: 30px;
200
+ border-radius: 5px;
201
+ background: #d4edda;
202
+ }
203
+ .password {
204
+ background: #f8f9fa;
205
+ padding: 15px;
206
+ border: 1px solid #dee2e6;
207
+ border-radius: 5px;
208
+ font-family: monospace;
209
+ font-size: 18px;
210
+ font-weight: bold;
211
+ text-align: center;
212
+ margin: 20px 0;
213
+ word-break: break-all;
214
+ }
215
+ .code {
216
+ background: #f4f4f4;
217
+ padding: 2px 6px;
218
+ border-radius: 3px;
219
+ font-family: monospace;
220
+ }
221
+ .warning {
222
+ background: #fff3cd;
223
+ border: 1px solid #ffeaa7;
224
+ padding: 15px;
225
+ border-radius: 5px;
226
+ margin: 20px 0;
227
+ }
228
+ </style>
229
+ </head>
230
+ <body>
231
+ <div class="container">
232
+ <h1>✅ Setup Completed Successfully!</h1>
233
+
234
+ <div class="warning">
235
+ <strong>⚠️ Important:</strong> Save this password immediately. It will not be shown again.
236
+ </div>
237
+
238
+ <h3>Generated Password:</h3>
239
+ <div class="password">${password}</div>
240
+
241
+ <h3>Next Steps:</h3>
242
+ <ol>
243
+ <li>
244
+ <strong>Set environment variable:</strong><br>
245
+ <span class="code">JOYSTICKDB_PASSWORD=${password}</span>
246
+ </li>
247
+ <li>
248
+ <strong>Restart your application</strong> to use the new password
249
+ </li>
250
+ <li>
251
+ <strong>Connect using the TCP client on port 1983</strong>
252
+ </li>
253
+ </ol>
254
+
255
+ <h3>Client Connection Example:</h3>
256
+ <pre style="background: #f4f4f4; padding: 15px; border-radius: 5px; overflow-x: auto;">
257
+ import joystickdb from '@joystickdb/client';
258
+
259
+ const client = joystickdb.client({
260
+ host: 'localhost',
261
+ port: 1983,
262
+ password: '${password}'
263
+ });
264
+
265
+ await client.ping();
266
+ </pre>
267
+
268
+ <h3>Configuration Details:</h3>
269
+ <ul>
270
+ <li>Configuration saved to: <span class="code">settings.db.json</span></li>
271
+ <li>TCP server running on port: <span class="code">1983</span></li>
272
+ <li>This setup interface is now disabled</li>
273
+ </ul>
274
+
275
+ <p><strong>Your JoystickDB server is ready to use!</strong></p>
276
+ </div>
277
+ </body>
278
+ </html>`;
279
+ };
280
+
281
+ /**
282
+ * Creates HTML content for error display.
283
+ * Generates an error page with troubleshooting information and navigation options.
284
+ * @param {string} error_message - Error message to display to user
285
+ * @returns {string} Complete HTML page content for error display
286
+ */
287
+ const create_error_html = (error_message) => {
288
+ return `<!DOCTYPE html>
289
+ <html>
290
+ <head>
291
+ <title>JoystickDB Setup Error</title>
292
+ <meta charset="utf-8">
293
+ <style>
294
+ body {
295
+ font-family: Arial, sans-serif;
296
+ max-width: 600px;
297
+ margin: 50px auto;
298
+ padding: 20px;
299
+ line-height: 1.6;
300
+ }
301
+ .container {
302
+ border: 1px solid #dc3545;
303
+ padding: 30px;
304
+ border-radius: 5px;
305
+ background: #f8d7da;
306
+ }
307
+ .code {
308
+ background: #f4f4f4;
309
+ padding: 2px 6px;
310
+ border-radius: 3px;
311
+ font-family: monospace;
312
+ }
313
+ </style>
314
+ </head>
315
+ <body>
316
+ <div class="container">
317
+ <h1>❌ Setup Error</h1>
318
+ <p><strong>Error:</strong> ${error_message}</p>
319
+
320
+ <h3>Troubleshooting:</h3>
321
+ <ul>
322
+ <li>If authentication is already configured, delete the <span class="code">authentication</span> section from <span class="code">settings.db.json</span></li>
323
+ <li>Ensure the server has write permissions to the current directory</li>
324
+ <li>Check server logs for additional error details</li>
325
+ <li>Restart the server and try again</li>
326
+ </ul>
327
+
328
+ <p><a href="javascript:history.back()">← Go Back</a></p>
329
+ </div>
330
+ </body>
331
+ </html>`;
332
+ };
333
+
334
+ /**
335
+ * Creates HTML content for emergency password recovery interface.
336
+ * Generates a password change form with validation, warnings, and instructions.
337
+ * @param {string} token - Recovery authentication token to include in form
338
+ * @param {string|null} [error=null] - Error message to display, if any
339
+ * @returns {string} Complete HTML page content for password recovery
340
+ */
341
+ const create_recovery_html = (token, error = null) => {
342
+ const error_html = error ? `<div style="color: red; margin-bottom: 20px; padding: 10px; border: 1px solid red; background: #ffe6e6;">${error}</div>` : '';
343
+
344
+ return `<!DOCTYPE html>
345
+ <html>
346
+ <head>
347
+ <title>JoystickDB Emergency Password Recovery</title>
348
+ <meta charset="utf-8">
349
+ <style>
350
+ body {
351
+ font-family: Arial, sans-serif;
352
+ max-width: 600px;
353
+ margin: 50px auto;
354
+ padding: 20px;
355
+ line-height: 1.6;
356
+ }
357
+ .container {
358
+ border: 1px solid #ffc107;
359
+ padding: 30px;
360
+ border-radius: 5px;
361
+ background: #fff3cd;
362
+ }
363
+ .button {
364
+ background: #dc3545;
365
+ color: white;
366
+ padding: 10px 20px;
367
+ border: none;
368
+ border-radius: 3px;
369
+ cursor: pointer;
370
+ font-size: 16px;
371
+ }
372
+ .button:hover {
373
+ background: #c82333;
374
+ }
375
+ .form-group {
376
+ margin-bottom: 20px;
377
+ }
378
+ .form-group label {
379
+ display: block;
380
+ margin-bottom: 5px;
381
+ font-weight: bold;
382
+ }
383
+ .form-group input {
384
+ width: 100%;
385
+ padding: 10px;
386
+ border: 1px solid #ddd;
387
+ border-radius: 3px;
388
+ font-size: 16px;
389
+ box-sizing: border-box;
390
+ }
391
+ .warning {
392
+ background: #f8d7da;
393
+ border: 1px solid #f5c6cb;
394
+ padding: 15px;
395
+ border-radius: 5px;
396
+ margin: 20px 0;
397
+ }
398
+ .code {
399
+ background: #f4f4f4;
400
+ padding: 2px 6px;
401
+ border-radius: 3px;
402
+ font-family: monospace;
403
+ }
404
+ </style>
405
+ <script>
406
+ function validateForm() {
407
+ const password = document.getElementById('password').value;
408
+ const confirmPassword = document.getElementById('confirm_password').value;
409
+
410
+ if (password.length < 12) {
411
+ alert('Password must be at least 12 characters long');
412
+ return false;
413
+ }
414
+
415
+ if (password !== confirmPassword) {
416
+ alert('Passwords do not match');
417
+ return false;
418
+ }
419
+
420
+ return confirm('Are you sure you want to change the database password? This will disconnect all existing clients.');
421
+ }
422
+ </script>
423
+ </head>
424
+ <body>
425
+ <div class="container">
426
+ <h1>🚨 Emergency Password Recovery</h1>
427
+ ${error_html}
428
+
429
+ <div class="warning">
430
+ <strong>⚠️ Warning:</strong> This will change your database password and disconnect all existing clients.
431
+ </div>
432
+
433
+ <p>Enter a new password for your JoystickDB server. The password must be at least 12 characters long.</p>
434
+
435
+ <form method="POST" action="/recovery?token=${token}" onsubmit="return validateForm()">
436
+ <div class="form-group">
437
+ <label for="password">New Password:</label>
438
+ <input type="password" id="password" name="password" required minlength="12" placeholder="Enter new password (minimum 12 characters)">
439
+ </div>
440
+
441
+ <div class="form-group">
442
+ <label for="confirm_password">Confirm Password:</label>
443
+ <input type="password" id="confirm_password" name="confirm_password" required minlength="12" placeholder="Confirm new password">
444
+ </div>
445
+
446
+ <button type="submit" class="button">Change Password</button>
447
+ </form>
448
+
449
+ <hr style="margin: 30px 0;">
450
+
451
+ <h3>What happens when you change the password?</h3>
452
+ <ul>
453
+ <li>The new password will be hashed and stored securely</li>
454
+ <li>All existing client connections will be terminated</li>
455
+ <li>Failed authentication attempts will be reset</li>
456
+ <li>This recovery token will be invalidated</li>
457
+ </ul>
458
+
459
+ <h3>After password change:</h3>
460
+ <ul>
461
+ <li>Update your environment variable: <span class="code">JOYSTICKDB_PASSWORD=&lt;new_password&gt;</span></li>
462
+ <li>Restart or redeploy your applications</li>
463
+ <li>Monitor logs for any unauthorized access attempts</li>
464
+ </ul>
465
+ </div>
466
+ </body>
467
+ </html>`;
468
+ };
469
+
470
+ /**
471
+ * Creates HTML content for successful password recovery completion.
472
+ * Displays confirmation and provides instructions for updating client applications.
473
+ * @param {string} timestamp - ISO timestamp of password change completion
474
+ * @returns {string} Complete HTML page content for recovery success
475
+ */
476
+ const create_recovery_success_html = (timestamp) => {
477
+ return `<!DOCTYPE html>
478
+ <html>
479
+ <head>
480
+ <title>JoystickDB Password Changed</title>
481
+ <meta charset="utf-8">
482
+ <style>
483
+ body {
484
+ font-family: Arial, sans-serif;
485
+ max-width: 600px;
486
+ margin: 50px auto;
487
+ padding: 20px;
488
+ line-height: 1.6;
489
+ }
490
+ .container {
491
+ border: 1px solid #28a745;
492
+ padding: 30px;
493
+ border-radius: 5px;
494
+ background: #d4edda;
495
+ }
496
+ .code {
497
+ background: #f4f4f4;
498
+ padding: 2px 6px;
499
+ border-radius: 3px;
500
+ font-family: monospace;
501
+ }
502
+ .warning {
503
+ background: #fff3cd;
504
+ border: 1px solid #ffeaa7;
505
+ padding: 15px;
506
+ border-radius: 5px;
507
+ margin: 20px 0;
508
+ }
509
+ </style>
510
+ </head>
511
+ <body>
512
+ <div class="container">
513
+ <h1>✅ Password Changed Successfully!</h1>
514
+
515
+ <p>New password is now active.</p>
516
+ <p>All existing connections have been terminated.</p>
517
+
518
+ <div class="warning">
519
+ <strong>⚠️ Important:</strong> Update your applications immediately to use the new password.
520
+ </div>
521
+
522
+ <h3>Next steps:</h3>
523
+ <ol>
524
+ <li>
525
+ <strong>Update your app's environment settings</strong> so the client can connect using your new password:<br>
526
+ <span class="code">JOYSTICKDB_PASSWORD=&lt;your_new_password&gt;</span>
527
+ </li>
528
+ <li>
529
+ <strong>Restart or redeploy your applications</strong> to use new password
530
+ </li>
531
+ <li>
532
+ <strong>Monitor logs</strong> for any unauthorized access attempts
533
+ </li>
534
+ </ol>
535
+
536
+ <h3>Recovery Details:</h3>
537
+ <ul>
538
+ <li>Recovery completed at: <span class="code">${timestamp}</span></li>
539
+ <li>All failed authentication attempts have been reset</li>
540
+ <li>Recovery token has been invalidated</li>
541
+ <li>TCP server continues running on port 1983</li>
542
+ </ul>
543
+
544
+ <p><strong>Your JoystickDB server is ready with the new password!</strong></p>
545
+ </div>
546
+ </body>
547
+ </html>`;
548
+ };
549
+
550
+ /**
551
+ * Parses URL-encoded form data from HTTP request body.
552
+ * Streams request body data and parses it into a key-value object using URLSearchParams.
553
+ * @param {http.IncomingMessage} req - HTTP request object with form data
554
+ * @returns {Promise<Object>} Parsed form data as key-value pairs
555
+ * @throws {Error} When form data parsing fails
556
+ */
557
+ const parse_form_data = (req) => {
558
+ return new Promise((resolve, reject) => {
559
+ let body = '';
560
+
561
+ req.on('data', (chunk) => {
562
+ body += chunk.toString();
563
+ });
564
+
565
+ req.on('end', () => {
566
+ try {
567
+ const params = new URLSearchParams(body);
568
+ const form_data = {};
569
+ for (const [key, value] of params) {
570
+ form_data[key] = value;
571
+ }
572
+ resolve(form_data);
573
+ } catch (error) {
574
+ reject(error);
575
+ }
576
+ });
577
+
578
+ req.on('error', (error) => {
579
+ reject(error);
580
+ });
581
+ });
582
+ };
583
+
584
+ /**
585
+ * Parses JSON data from HTTP request body.
586
+ * Streams request body data and parses it as JSON.
587
+ * @param {http.IncomingMessage} req - HTTP request object with JSON data
588
+ * @returns {Promise<Object>} Parsed JSON data
589
+ * @throws {Error} When JSON parsing fails
590
+ */
591
+ const parse_json_data = (req) => {
592
+ return new Promise((resolve, reject) => {
593
+ let body = '';
594
+
595
+ req.on('data', (chunk) => {
596
+ body += chunk.toString();
597
+ });
598
+
599
+ req.on('end', () => {
600
+ try {
601
+ const json_data = JSON.parse(body);
602
+ resolve(json_data);
603
+ } catch (error) {
604
+ reject(error);
605
+ }
606
+ });
607
+
608
+ req.on('error', (error) => {
609
+ reject(error);
610
+ });
611
+ });
612
+ };
613
+
614
+ /**
615
+ * Validates API key from request headers.
616
+ * @param {http.IncomingMessage} req - HTTP request object
617
+ * @returns {boolean} True if API key is valid
618
+ */
619
+ const validate_request_api_key = (req) => {
620
+ const api_key = req.headers['x-joystick-db-api-key'];
621
+ return validate_api_key(api_key);
622
+ };
623
+
624
+ /**
625
+ * Sends JSON response with appropriate headers.
626
+ * @param {http.ServerResponse} res - HTTP response object
627
+ * @param {number} status_code - HTTP status code
628
+ * @param {Object} data - Data to send as JSON
629
+ */
630
+ const send_json_response = (res, status_code, data) => {
631
+ res.writeHead(status_code, { 'Content-Type': 'application/json' });
632
+ res.end(JSON.stringify(data));
633
+ };
634
+
635
+ /**
636
+ * Handles user creation API endpoint.
637
+ * @param {http.IncomingMessage} req - HTTP request object
638
+ * @param {http.ServerResponse} res - HTTP response object
639
+ * @returns {Promise<void>}
640
+ */
641
+ const handle_create_user = async (req, res) => {
642
+ const client_ip = req.socket.remoteAddress || '127.0.0.1';
643
+
644
+ if (!validate_request_api_key(req)) {
645
+ log.warn('Invalid API key for user creation', { client_ip });
646
+ send_json_response(res, 403, {
647
+ error: 'Database setup incomplete. A valid API key must be passed until an admin user has been created.'
648
+ });
649
+ return;
650
+ }
651
+
652
+ try {
653
+ const user_data = await parse_json_data(req);
654
+ const created_user = await create_user(user_data);
655
+
656
+ log.info('User created via API', { username: created_user.username, client_ip });
657
+
658
+ send_json_response(res, 201, {
659
+ ok: 1,
660
+ user: created_user
661
+ });
662
+ } catch (error) {
663
+ log.error('User creation failed via API', { client_ip, error: error.message });
664
+ send_json_response(res, 400, {
665
+ error: error.message
666
+ });
667
+ }
668
+ };
669
+
670
+ /**
671
+ * Handles get all users API endpoint.
672
+ * @param {http.IncomingMessage} req - HTTP request object
673
+ * @param {http.ServerResponse} res - HTTP response object
674
+ * @returns {Promise<void>}
675
+ */
676
+ const handle_get_users = async (req, res) => {
677
+ const client_ip = req.socket.remoteAddress || '127.0.0.1';
678
+
679
+ if (!validate_request_api_key(req)) {
680
+ log.warn('Invalid API key for get users', { client_ip });
681
+ send_json_response(res, 403, {
682
+ error: 'Database setup incomplete. A valid API key must be passed until an admin user has been created.'
683
+ });
684
+ return;
685
+ }
686
+
687
+ try {
688
+ const users = get_all_users();
689
+
690
+ log.info('Users retrieved via API', { count: users.length, client_ip });
691
+
692
+ send_json_response(res, 200, {
693
+ ok: 1,
694
+ users: users
695
+ });
696
+ } catch (error) {
697
+ log.error('Get users failed via API', { client_ip, error: error.message });
698
+ send_json_response(res, 500, {
699
+ error: error.message
700
+ });
701
+ }
702
+ };
703
+
704
+ /**
705
+ * Handles update user API endpoint.
706
+ * @param {http.IncomingMessage} req - HTTP request object
707
+ * @param {http.ServerResponse} res - HTTP response object
708
+ * @param {string} username - Username to update
709
+ * @returns {Promise<void>}
710
+ */
711
+ const handle_update_user = async (req, res, username) => {
712
+ const client_ip = req.socket.remoteAddress || '127.0.0.1';
713
+
714
+ if (!validate_request_api_key(req)) {
715
+ log.warn('Invalid API key for user update', { client_ip, username });
716
+ send_json_response(res, 403, {
717
+ error: 'Database setup incomplete. A valid API key must be passed until an admin user has been created.'
718
+ });
719
+ return;
720
+ }
721
+
722
+ try {
723
+ const updates = await parse_json_data(req);
724
+ const updated_user = await update_user(username, updates);
725
+
726
+ log.info('User updated via API', { username, client_ip });
727
+
728
+ send_json_response(res, 200, {
729
+ ok: 1,
730
+ user: updated_user
731
+ });
732
+ } catch (error) {
733
+ log.error('User update failed via API', { client_ip, username, error: error.message });
734
+
735
+ const status_code = error.message === 'User not found' ? 404 : 400;
736
+ send_json_response(res, status_code, {
737
+ error: error.message
738
+ });
739
+ }
740
+ };
741
+
742
+ /**
743
+ * Handles delete user API endpoint.
744
+ * @param {http.IncomingMessage} req - HTTP request object
745
+ * @param {http.ServerResponse} res - HTTP response object
746
+ * @param {string} username - Username to delete
747
+ * @returns {Promise<void>}
748
+ */
749
+ const handle_delete_user = async (req, res, username) => {
750
+ const client_ip = req.socket.remoteAddress || '127.0.0.1';
751
+
752
+ if (!validate_request_api_key(req)) {
753
+ log.warn('Invalid API key for user deletion', { client_ip, username });
754
+ send_json_response(res, 403, {
755
+ error: 'Database setup incomplete. A valid API key must be passed until an admin user has been created.'
756
+ });
757
+ return;
758
+ }
759
+
760
+ try {
761
+ delete_user(username);
762
+
763
+ log.info('User deleted via API', { username, client_ip });
764
+
765
+ send_json_response(res, 200, {
766
+ ok: 1,
767
+ message: 'User deleted successfully'
768
+ });
769
+ } catch (error) {
770
+ log.error('User deletion failed via API', { client_ip, username, error: error.message });
771
+
772
+ const status_code = error.message === 'User not found' ? 404 : 400;
773
+ send_json_response(res, status_code, {
774
+ error: error.message
775
+ });
776
+ }
777
+ };
778
+
779
+ /**
780
+ * Handles user management API requests.
781
+ * @param {http.IncomingMessage} req - HTTP request object
782
+ * @param {http.ServerResponse} res - HTTP response object
783
+ * @param {string} path_parts - URL path parts after /api/users
784
+ * @returns {Promise<void>}
785
+ */
786
+ const handle_users_api = async (req, res, path_parts) => {
787
+ if (req.method === 'POST' && path_parts.length === 0) {
788
+ await handle_create_user(req, res);
789
+ return;
790
+ }
791
+
792
+ if (req.method === 'GET' && path_parts.length === 0) {
793
+ await handle_get_users(req, res);
794
+ return;
795
+ }
796
+
797
+ if (req.method === 'PUT' && path_parts.length === 1) {
798
+ const username = path_parts[0];
799
+ await handle_update_user(req, res, username);
800
+ return;
801
+ }
802
+
803
+ if (req.method === 'DELETE' && path_parts.length === 1) {
804
+ const username = path_parts[0];
805
+ await handle_delete_user(req, res, username);
806
+ return;
807
+ }
808
+
809
+ send_json_response(res, 405, {
810
+ error: 'Method not allowed'
811
+ });
812
+ };
813
+
814
+ /**
815
+ * Handles HTTP requests for database setup operations.
816
+ * Processes both GET (display form) and POST (execute setup) requests with rate limiting,
817
+ * token validation, and setup completion. Generates secure passwords and saves configuration.
818
+ * @param {http.IncomingMessage} req - HTTP request object
819
+ * @param {http.ServerResponse} res - HTTP response object
820
+ * @param {string} token_param - Setup token from URL parameters
821
+ * @returns {Promise<void>}
822
+ */
823
+ const handle_setup_request = async (req, res, token_param) => {
824
+ const client_ip = req.socket.remoteAddress || '127.0.0.1';
825
+
826
+ if (is_rate_limited(client_ip)) {
827
+ res.writeHead(429, { 'Content-Type': 'text/html' });
828
+ res.end(create_error_html('Too many setup attempts. Please try again later.'));
829
+ return;
830
+ }
831
+
832
+ record_setup_attempt(client_ip);
833
+
834
+ if (!is_setup_required()) {
835
+ res.writeHead(400, { 'Content-Type': 'text/html' });
836
+ res.end(create_error_html('Setup has already been completed.'));
837
+ return;
838
+ }
839
+
840
+ if (!setup_token || token_param !== setup_token) {
841
+ log.warn('Invalid setup token attempt', { client_ip, provided_token: token_param });
842
+ res.writeHead(403, { 'Content-Type': 'text/html' });
843
+ res.end(create_error_html('Invalid or missing setup token.'));
844
+ return;
845
+ }
846
+
847
+ if (req.method === 'GET') {
848
+ res.writeHead(200, { 'Content-Type': 'text/html' });
849
+ res.end(create_setup_html(setup_token));
850
+ return;
851
+ }
852
+
853
+ if (req.method === 'POST') {
854
+ try {
855
+ const password = setup_authentication();
856
+ setup_completed = true;
857
+ setup_token = null;
858
+
859
+ log.info('Setup completed successfully via HTTP interface', { client_ip });
860
+
861
+ res.writeHead(200, { 'Content-Type': 'text/html' });
862
+ res.end(create_success_html(password));
863
+ } catch (error) {
864
+ log.error('Setup failed via HTTP interface', { client_ip, error: error.message });
865
+ res.writeHead(500, { 'Content-Type': 'text/html' });
866
+ res.end(create_error_html(error.message));
867
+ }
868
+ return;
869
+ }
870
+
871
+ res.writeHead(405, { 'Content-Type': 'text/html' });
872
+ res.end(create_error_html('Method not allowed.'));
873
+ };
874
+
875
+ /**
876
+ * Handles HTTP requests for emergency password recovery operations.
877
+ * Processes both GET (display form) and POST (execute recovery) requests with token validation,
878
+ * password validation, and connection termination. Updates authentication configuration.
879
+ * @param {http.IncomingMessage} req - HTTP request object
880
+ * @param {http.ServerResponse} res - HTTP response object
881
+ * @param {string} token_param - Recovery token from URL parameters
882
+ * @returns {Promise<void>}
883
+ */
884
+ const handle_recovery_request = async (req, res, token_param) => {
885
+ const client_ip = req.socket.remoteAddress || '127.0.0.1';
886
+
887
+ log.info('Recovery request received', { client_ip, method: req.method });
888
+
889
+ if (!token_param) {
890
+ res.writeHead(400, { 'Content-Type': 'text/html' });
891
+ res.end(create_error_html('Recovery token is required.'));
892
+ return;
893
+ }
894
+
895
+ const token_validation = is_token_valid(token_param);
896
+
897
+ if (!token_validation.valid) {
898
+ record_failed_recovery_attempt(client_ip);
899
+
900
+ let error_message = 'Invalid recovery token.';
901
+ if (token_validation.reason === 'expired') {
902
+ error_message = 'Recovery token has expired. Generate a new token using --generate-recovery-token.';
903
+ } else if (token_validation.reason === 'locked') {
904
+ error_message = 'Recovery is locked due to too many failed attempts. Please try again later.';
905
+ } else if (token_validation.reason === 'no_token') {
906
+ error_message = 'No active recovery token found. Generate a new token using --generate-recovery-token.';
907
+ }
908
+
909
+ log.warn('Invalid recovery token attempt', {
910
+ client_ip,
911
+ reason: token_validation.reason,
912
+ provided_token: token_param
913
+ });
914
+
915
+ res.writeHead(403, { 'Content-Type': 'text/html' });
916
+ res.end(create_error_html(error_message));
917
+ return;
918
+ }
919
+
920
+ if (req.method === 'GET') {
921
+ res.writeHead(200, { 'Content-Type': 'text/html' });
922
+ res.end(create_recovery_html(token_param));
923
+ return;
924
+ }
925
+
926
+ if (req.method === 'POST') {
927
+ try {
928
+ const form_data = await parse_form_data(req);
929
+ const { password, confirm_password } = form_data;
930
+
931
+ if (!password || !confirm_password) {
932
+ res.writeHead(400, { 'Content-Type': 'text/html' });
933
+ res.end(create_recovery_html(token_param, 'Both password fields are required.'));
934
+ return;
935
+ }
936
+
937
+ if (password !== confirm_password) {
938
+ res.writeHead(400, { 'Content-Type': 'text/html' });
939
+ res.end(create_recovery_html(token_param, 'Passwords do not match.'));
940
+ return;
941
+ }
942
+
943
+ // NOTE: Create connection terminator function to disconnect all clients.
944
+ const connection_terminator = () => {
945
+ // NOTE: This will be called by the recovery manager to terminate connections.
946
+ // For now, we'll just log that connections should be terminated.
947
+ // The actual termination will be handled by the server when it detects the password change.
948
+ log.info('Password change completed, existing connections should be terminated');
949
+ };
950
+
951
+ const result = await change_password(password, client_ip, connection_terminator);
952
+
953
+ log.info('Emergency password change completed via HTTP interface', {
954
+ client_ip,
955
+ timestamp: result.timestamp
956
+ });
957
+
958
+ res.writeHead(200, { 'Content-Type': 'text/html' });
959
+ res.end(create_recovery_success_html(result.timestamp));
960
+ } catch (error) {
961
+ log.error('Emergency password change failed via HTTP interface', {
962
+ client_ip,
963
+ error: error.message
964
+ });
965
+
966
+ res.writeHead(400, { 'Content-Type': 'text/html' });
967
+ res.end(create_recovery_html(token_param, error.message));
968
+ }
969
+ return;
970
+ }
971
+
972
+ res.writeHead(405, { 'Content-Type': 'text/html' });
973
+ res.end(create_error_html('Method not allowed.'));
974
+ };
975
+
976
+ /**
977
+ * Creates HTTP server instance for setup and recovery operations.
978
+ * Sets up request routing, error handling, and connection tracking for proper cleanup.
979
+ * Handles /setup and /recovery endpoints with appropriate authentication and validation.
980
+ * @param {number} [port=1984] - Port number for HTTP server
981
+ * @returns {http.Server} Configured HTTP server instance
982
+ */
983
+ const create_http_server = (port = 1984) => {
984
+ // NOTE: Create a new server instance but don't assign to global yet.
985
+ const server = http.createServer(async (req, res) => {
986
+ try {
987
+ const url = new URL(req.url, `http://localhost:${port}`);
988
+
989
+ if (url.pathname === '/setup') {
990
+ const token_param = url.searchParams.get('token');
991
+ await handle_setup_request(req, res, token_param);
992
+ return;
993
+ }
994
+
995
+ if (url.pathname === '/recovery') {
996
+ const token_param = url.searchParams.get('token');
997
+ await handle_recovery_request(req, res, token_param);
998
+ return;
999
+ }
1000
+
1001
+ if (url.pathname.startsWith('/api/users')) {
1002
+ const path_parts = url.pathname.split('/').slice(3); // Remove '', 'api', 'users'
1003
+ await handle_users_api(req, res, path_parts);
1004
+ return;
1005
+ }
1006
+
1007
+ res.writeHead(404, { 'Content-Type': 'text/html' });
1008
+ res.end(`<!DOCTYPE html>
1009
+ <html>
1010
+ <head><title>404 Not Found</title></head>
1011
+ <body>
1012
+ <h1>404 Not Found</h1>
1013
+ <p>The requested resource was not found on this server.</p>
1014
+ </body>
1015
+ </html>`);
1016
+ } catch (error) {
1017
+ log.error('HTTP request error', { error: error.message, url: req.url });
1018
+ res.writeHead(500, { 'Content-Type': 'text/html' });
1019
+ res.end(create_error_html('Internal server error.'));
1020
+ }
1021
+ });
1022
+
1023
+ // NOTE: Track connections for proper cleanup.
1024
+ const connections = new Set();
1025
+
1026
+ server.on('connection', (connection) => {
1027
+ connections.add(connection);
1028
+ connection.on('close', () => {
1029
+ connections.delete(connection);
1030
+ });
1031
+ });
1032
+
1033
+ // NOTE: Store connections reference for cleanup.
1034
+ server._connections = connections;
1035
+
1036
+ server.on('error', (error) => {
1037
+ log.error('HTTP server error', { error: error.message });
1038
+ });
1039
+
1040
+ return server;
1041
+ };
1042
+
1043
+ /**
1044
+ * Starts the HTTP server for setup and recovery operations.
1045
+ * Generates setup token if needed, creates server instance, and handles startup errors.
1046
+ * Logs appropriate messages for setup requirements and server status.
1047
+ * @param {number} [port=1984] - Port number to start HTTP server on
1048
+ * @returns {Promise<http.Server>} Promise resolving to started HTTP server
1049
+ * @throws {Error} When server fails to start or port is unavailable
1050
+ */
1051
+ const start_http_server = (port = 1984) => {
1052
+ const setup_required = is_setup_required();
1053
+
1054
+ if (setup_required) {
1055
+ setup_token = generate_setup_token();
1056
+ setup_completed = false;
1057
+ }
1058
+
1059
+ const server = create_http_server(port);
1060
+
1061
+ return new Promise((resolve, reject) => {
1062
+ // NOTE: Set up error handler before calling listen.
1063
+ server.once('error', (error) => {
1064
+ // NOTE: Clean up on startup failure.
1065
+ if (setup_required) {
1066
+ setup_token = null;
1067
+ setup_completed = false;
1068
+ }
1069
+
1070
+ log.error('Failed to start HTTP server', { port, error: error.message });
1071
+ reject(error);
1072
+ });
1073
+
1074
+ server.listen(port, () => {
1075
+ // NOTE: Only assign to global variable after successful startup.
1076
+ http_server = server;
1077
+
1078
+ if (setup_required) {
1079
+ log.info('JoystickDB Setup Required');
1080
+ log.info(`Visit: http://localhost:${port}/setup?token=${setup_token}`);
1081
+ } else {
1082
+ log.info('HTTP server started for recovery operations', { port });
1083
+ }
1084
+
1085
+ resolve(server);
1086
+ });
1087
+ });
1088
+ };
1089
+
1090
+ /**
1091
+ * Stops the HTTP server and cleans up all resources.
1092
+ * Terminates all active connections, clears rate limiting data, and resets setup state.
1093
+ * Provides graceful shutdown with timeout fallback for forced termination.
1094
+ * @returns {Promise<void>} Promise resolving when server is fully stopped
1095
+ */
1096
+ const stop_http_server = () => {
1097
+ return new Promise((resolve) => {
1098
+ if (!http_server) {
1099
+ resolve();
1100
+ return;
1101
+ }
1102
+
1103
+ const server_to_close = http_server;
1104
+ const connections = server_to_close._connections || new Set();
1105
+
1106
+ http_server = null;
1107
+ setup_token = null;
1108
+ setup_completed = false;
1109
+ rate_limit_attempts.clear();
1110
+
1111
+ // NOTE: Force close all existing connections.
1112
+ connections.forEach(connection => {
1113
+ try {
1114
+ connection.destroy();
1115
+ } catch (error) {
1116
+ // NOTE: Ignore errors when destroying connections.
1117
+ }
1118
+ });
1119
+
1120
+ server_to_close.close((error) => {
1121
+ if (error) {
1122
+ log.warn('HTTP server close error', { error: error.message });
1123
+ } else {
1124
+ log.info('HTTP server stopped');
1125
+ }
1126
+
1127
+ // NOTE: Add delay to ensure port is fully released.
1128
+ setTimeout(() => {
1129
+ resolve();
1130
+ }, 250);
1131
+ });
1132
+
1133
+ // NOTE: Force resolve if server doesn't close gracefully within 2 seconds.
1134
+ setTimeout(() => {
1135
+ log.warn('HTTP server forced shutdown after timeout');
1136
+ resolve();
1137
+ }, 2000);
1138
+ });
1139
+ };
1140
+
1141
+ /**
1142
+ * Gets current setup and server status information.
1143
+ * Returns comprehensive status including setup requirements, token availability,
1144
+ * completion status, and HTTP server running state.
1145
+ * @returns {Object} Setup status information
1146
+ * @returns {boolean} returns.setup_required - Whether initial setup is needed
1147
+ * @returns {string|null} returns.setup_token - Current setup token if available
1148
+ * @returns {boolean} returns.setup_completed - Whether setup has been completed
1149
+ * @returns {boolean} returns.http_server_running - Whether HTTP server is active
1150
+ */
1151
+ const get_setup_info = () => {
1152
+ return {
1153
+ setup_required: is_setup_required(),
1154
+ setup_token: setup_token,
1155
+ setup_completed: setup_completed,
1156
+ http_server_running: !!http_server
1157
+ };
1158
+ };
1159
+
1160
+ export {
1161
+ start_http_server,
1162
+ stop_http_server,
1163
+ get_setup_info,
1164
+ is_setup_required
1165
+ };