@resolveio/server-lib 22.3.221 → 22.3.223

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 (745) hide show
  1. package/ai/assistant-core-heuristics.d.ts +11 -0
  2. package/ai/assistant-core-heuristics.js +356 -0
  3. package/ai/assistant-core-heuristics.js.map +1 -0
  4. package/ai/resolveio-platform-intelligence-memory-corpus.d.ts +3 -0
  5. package/ai/resolveio-platform-intelligence-memory-corpus.js +214 -0
  6. package/ai/resolveio-platform-intelligence-memory-corpus.js.map +1 -0
  7. package/ai/resolveio-platform-intelligence-memory.d.ts +20 -0
  8. package/ai/resolveio-platform-intelligence-memory.js +341 -0
  9. package/ai/resolveio-platform-intelligence-memory.js.map +1 -0
  10. package/{src/ai/resolveio-platform-intelligence-types.ts → ai/resolveio-platform-intelligence-types.d.ts} +15 -20
  11. package/ai/resolveio-platform-intelligence-types.js +4 -0
  12. package/ai/resolveio-platform-intelligence-types.js.map +1 -0
  13. package/ai/resolveio-platform-intelligence.d.ts +6 -0
  14. package/ai/resolveio-platform-intelligence.js +463 -0
  15. package/ai/resolveio-platform-intelligence.js.map +1 -0
  16. package/client-server-app.d.ts +1 -0
  17. package/client-server-app.js +68 -0
  18. package/client-server-app.js.map +1 -0
  19. package/collections/ai-run.collection.d.ts +3 -0
  20. package/collections/ai-run.collection.js +170 -0
  21. package/collections/ai-run.collection.js.map +1 -0
  22. package/collections/ai-terminal-conversation.collection.d.ts +2 -0
  23. package/collections/ai-terminal-conversation.collection.js +140 -0
  24. package/collections/ai-terminal-conversation.collection.js.map +1 -0
  25. package/collections/ai-terminal-issue-report.collection.d.ts +2 -0
  26. package/collections/ai-terminal-issue-report.collection.js +148 -0
  27. package/collections/ai-terminal-issue-report.collection.js.map +1 -0
  28. package/collections/ai-terminal-message.collection.d.ts +2 -0
  29. package/collections/ai-terminal-message.collection.js +121 -0
  30. package/collections/ai-terminal-message.collection.js.map +1 -0
  31. package/collections/app-setting.collection.d.ts +3 -0
  32. package/collections/app-setting.collection.js +103 -0
  33. package/collections/app-setting.collection.js.map +1 -0
  34. package/collections/app-status.collection.d.ts +3 -0
  35. package/collections/app-status.collection.js +57 -0
  36. package/collections/app-status.collection.js.map +1 -0
  37. package/collections/communication-metric.collection.d.ts +2 -0
  38. package/collections/communication-metric.collection.js +133 -0
  39. package/collections/communication-metric.collection.js.map +1 -0
  40. package/collections/counter.collection.d.ts +3 -0
  41. package/collections/counter.collection.js +56 -0
  42. package/collections/counter.collection.js.map +1 -0
  43. package/collections/cron-job-history.collection.d.ts +3 -0
  44. package/collections/cron-job-history.collection.js +137 -0
  45. package/collections/cron-job-history.collection.js.map +1 -0
  46. package/collections/cron-job.collection.d.ts +3 -0
  47. package/collections/cron-job.collection.js +92 -0
  48. package/collections/cron-job.collection.js.map +1 -0
  49. package/collections/customer-notification.collection.d.ts +3 -0
  50. package/collections/customer-notification.collection.js +130 -0
  51. package/collections/customer-notification.collection.js.map +1 -0
  52. package/collections/customer-portal-password.collection.d.ts +3 -0
  53. package/collections/customer-portal-password.collection.js +75 -0
  54. package/collections/customer-portal-password.collection.js.map +1 -0
  55. package/collections/email-history.collection.d.ts +3 -0
  56. package/collections/email-history.collection.js +134 -0
  57. package/collections/email-history.collection.js.map +1 -0
  58. package/collections/email-verified.collection.d.ts +3 -0
  59. package/collections/email-verified.collection.js +62 -0
  60. package/collections/email-verified.collection.js.map +1 -0
  61. package/collections/file.collection.d.ts +3 -0
  62. package/collections/file.collection.js +74 -0
  63. package/collections/file.collection.js.map +1 -0
  64. package/collections/flag-update.collection.d.ts +3 -0
  65. package/collections/flag-update.collection.js +57 -0
  66. package/collections/flag-update.collection.js.map +1 -0
  67. package/collections/flag.collection.d.ts +3 -0
  68. package/collections/flag.collection.js +57 -0
  69. package/collections/flag.collection.js.map +1 -0
  70. package/collections/log-method-latency.collection.d.ts +3 -0
  71. package/collections/log-method-latency.collection.js +77 -0
  72. package/collections/log-method-latency.collection.js.map +1 -0
  73. package/collections/log-subscription.collection.d.ts +3 -0
  74. package/collections/log-subscription.collection.js +80 -0
  75. package/collections/log-subscription.collection.js.map +1 -0
  76. package/collections/log.collection.d.ts +3 -0
  77. package/collections/log.collection.js +93 -0
  78. package/collections/log.collection.js.map +1 -0
  79. package/collections/logged-in-users.collection.d.ts +3 -0
  80. package/collections/logged-in-users.collection.js +67 -0
  81. package/collections/logged-in-users.collection.js.map +1 -0
  82. package/collections/monitor-cpu.collection.d.ts +3 -0
  83. package/collections/monitor-cpu.collection.js +65 -0
  84. package/collections/monitor-cpu.collection.js.map +1 -0
  85. package/collections/monitor-function.collection.d.ts +3 -0
  86. package/collections/monitor-function.collection.js +74 -0
  87. package/collections/monitor-function.collection.js.map +1 -0
  88. package/collections/monitor-memory.collection.d.ts +3 -0
  89. package/collections/monitor-memory.collection.js +77 -0
  90. package/collections/monitor-memory.collection.js.map +1 -0
  91. package/collections/monitor-mongo.collection.d.ts +3 -0
  92. package/collections/monitor-mongo.collection.js +71 -0
  93. package/collections/monitor-mongo.collection.js.map +1 -0
  94. package/collections/notification.collection.d.ts +3 -0
  95. package/collections/notification.collection.js +57 -0
  96. package/collections/notification.collection.js.map +1 -0
  97. package/collections/openai-usage-ledger.collection.d.ts +2 -0
  98. package/collections/openai-usage-ledger.collection.js +188 -0
  99. package/collections/openai-usage-ledger.collection.js.map +1 -0
  100. package/collections/report-builder-dashboard-builder.collection.d.ts +3 -0
  101. package/collections/report-builder-dashboard-builder.collection.js +109 -0
  102. package/collections/report-builder-dashboard-builder.collection.js.map +1 -0
  103. package/collections/report-builder-library.collection.d.ts +3 -0
  104. package/collections/report-builder-library.collection.js +87 -0
  105. package/collections/report-builder-library.collection.js.map +1 -0
  106. package/collections/report-builder-report.collection.d.ts +4 -0
  107. package/collections/report-builder-report.collection.js +184 -0
  108. package/collections/report-builder-report.collection.js.map +1 -0
  109. package/collections/user-group.collection.d.ts +4 -0
  110. package/collections/user-group.collection.js +89 -0
  111. package/collections/user-group.collection.js.map +1 -0
  112. package/collections/user-guide.collection.d.ts +3 -0
  113. package/collections/user-guide.collection.js +57 -0
  114. package/collections/user-guide.collection.js.map +1 -0
  115. package/collections/user.collection.d.ts +4 -0
  116. package/collections/user.collection.js +180 -0
  117. package/collections/user.collection.js.map +1 -0
  118. package/cron/cron.d.ts +14 -0
  119. package/cron/cron.js +216 -0
  120. package/cron/cron.js.map +1 -0
  121. package/fixtures/cron-jobs.d.ts +1 -0
  122. package/fixtures/cron-jobs.js +150 -0
  123. package/fixtures/cron-jobs.js.map +1 -0
  124. package/fixtures/init.d.ts +1 -0
  125. package/fixtures/init.js +91 -0
  126. package/fixtures/init.js.map +1 -0
  127. package/http/auth.d.ts +2 -0
  128. package/http/auth.js +951 -0
  129. package/http/auth.js.map +1 -0
  130. package/http/health.d.ts +1 -0
  131. package/http/health.js +11 -0
  132. package/http/health.js.map +1 -0
  133. package/http/home.d.ts +1 -0
  134. package/http/home.js +134 -0
  135. package/http/home.js.map +1 -0
  136. package/http/slow-query-publication.d.ts +2 -0
  137. package/http/slow-query-publication.js +99 -0
  138. package/http/slow-query-publication.js.map +1 -0
  139. package/index.d.ts +1 -0
  140. package/index.js +19 -0
  141. package/index.js.map +1 -0
  142. package/managers/ai-assistant-codex-manager.manager.d.ts +67 -0
  143. package/managers/ai-assistant-codex-manager.manager.js +1113 -0
  144. package/managers/ai-assistant-codex-manager.manager.js.map +1 -0
  145. package/managers/ai-run-evidence.manager.d.ts +36 -0
  146. package/managers/ai-run-evidence.manager.js +377 -0
  147. package/managers/ai-run-evidence.manager.js.map +1 -0
  148. package/managers/communication-metric.manager.d.ts +16 -0
  149. package/managers/communication-metric.manager.js +134 -0
  150. package/managers/communication-metric.manager.js.map +1 -0
  151. package/managers/cron.manager.d.ts +20 -0
  152. package/managers/cron.manager.js +534 -0
  153. package/managers/cron.manager.js.map +1 -0
  154. package/managers/customer-notification-content.manager.d.ts +55 -0
  155. package/managers/customer-notification-content.manager.js +158 -0
  156. package/managers/customer-notification-content.manager.js.map +1 -0
  157. package/managers/diagnostic-manager-bootstrap.d.ts +9 -0
  158. package/managers/diagnostic-manager-bootstrap.js +260 -0
  159. package/managers/diagnostic-manager-bootstrap.js.map +1 -0
  160. package/managers/error-auto-fix.manager.d.ts +149 -0
  161. package/managers/error-auto-fix.manager.js +3064 -0
  162. package/managers/error-auto-fix.manager.js.map +1 -0
  163. package/managers/local-log.manager.d.ts +18 -0
  164. package/managers/local-log.manager.js +88 -0
  165. package/managers/local-log.manager.js.map +1 -0
  166. package/managers/method.manager.d.ts +84 -0
  167. package/managers/method.manager.js +1964 -0
  168. package/managers/method.manager.js.map +1 -0
  169. package/managers/mongo.manager.d.ts +224 -0
  170. package/managers/mongo.manager.js +5000 -0
  171. package/managers/mongo.manager.js.map +1 -0
  172. package/managers/monitor.manager.d.ts +70 -0
  173. package/managers/monitor.manager.js +550 -0
  174. package/managers/monitor.manager.js.map +1 -0
  175. package/managers/openai-usage-ledger.manager.d.ts +30 -0
  176. package/managers/openai-usage-ledger.manager.js +142 -0
  177. package/managers/openai-usage-ledger.manager.js.map +1 -0
  178. package/managers/slow-query-verifier.manager.d.ts +144 -0
  179. package/managers/slow-query-verifier.manager.js +3857 -0
  180. package/managers/slow-query-verifier.manager.js.map +1 -0
  181. package/managers/slow-query.manager.d.ts +28 -0
  182. package/managers/slow-query.manager.js +468 -0
  183. package/managers/slow-query.manager.js.map +1 -0
  184. package/managers/subscription.manager.d.ts +169 -0
  185. package/managers/subscription.manager.js +3434 -0
  186. package/managers/subscription.manager.js.map +1 -0
  187. package/managers/websocket.manager.d.ts +73 -0
  188. package/managers/websocket.manager.js +673 -0
  189. package/managers/websocket.manager.js.map +1 -0
  190. package/managers/worker-dispatcher.manager.d.ts +120 -0
  191. package/managers/worker-dispatcher.manager.js +1266 -0
  192. package/managers/worker-dispatcher.manager.js.map +1 -0
  193. package/managers/worker-server.manager.d.ts +35 -0
  194. package/managers/worker-server.manager.js +582 -0
  195. package/managers/worker-server.manager.js.map +1 -0
  196. package/methods/accounts.d.ts +2 -0
  197. package/methods/accounts.js +624 -0
  198. package/methods/accounts.js.map +1 -0
  199. package/methods/ai-terminal.d.ts +458 -0
  200. package/methods/ai-terminal.js +27991 -0
  201. package/methods/ai-terminal.js.map +1 -0
  202. package/methods/app-settings.d.ts +2 -0
  203. package/methods/app-settings.js +169 -0
  204. package/methods/app-settings.js.map +1 -0
  205. package/methods/aws.d.ts +2 -0
  206. package/methods/aws.js +877 -0
  207. package/methods/aws.js.map +1 -0
  208. package/methods/collections.d.ts +2 -0
  209. package/methods/collections.js +719 -0
  210. package/methods/collections.js.map +1 -0
  211. package/methods/counters.d.ts +2 -0
  212. package/methods/counters.js +113 -0
  213. package/methods/counters.js.map +1 -0
  214. package/methods/cron-jobs.d.ts +2 -0
  215. package/methods/cron-jobs.js +2475 -0
  216. package/methods/cron-jobs.js.map +1 -0
  217. package/methods/customer-notifications.d.ts +2 -0
  218. package/methods/customer-notifications.js +528 -0
  219. package/methods/customer-notifications.js.map +1 -0
  220. package/methods/diagnostics.d.ts +2 -0
  221. package/methods/diagnostics.js +703 -0
  222. package/methods/diagnostics.js.map +1 -0
  223. package/methods/flag-updates.d.ts +2 -0
  224. package/methods/flag-updates.js +8 -0
  225. package/methods/flag-updates.js.map +1 -0
  226. package/methods/flags.d.ts +2 -0
  227. package/methods/flags.js +8 -0
  228. package/methods/flags.js.map +1 -0
  229. package/methods/logs.d.ts +2 -0
  230. package/methods/logs.js +751 -0
  231. package/methods/logs.js.map +1 -0
  232. package/methods/mongo-explorer.d.ts +2 -0
  233. package/methods/mongo-explorer.js +1808 -0
  234. package/methods/mongo-explorer.js.map +1 -0
  235. package/methods/monitor.d.ts +2 -0
  236. package/methods/monitor.js +543 -0
  237. package/methods/monitor.js.map +1 -0
  238. package/methods/pdf.d.ts +2 -0
  239. package/methods/pdf.js +1216 -0
  240. package/methods/pdf.js.map +1 -0
  241. package/methods/publications.d.ts +1 -0
  242. package/methods/publications.js +183 -0
  243. package/methods/publications.js.map +1 -0
  244. package/methods/report-builder.d.ts +2 -0
  245. package/methods/report-builder.js +3094 -0
  246. package/methods/report-builder.js.map +1 -0
  247. package/methods/support.d.ts +2 -0
  248. package/methods/support.js +430 -0
  249. package/methods/support.js.map +1 -0
  250. package/models/ai-run.model.d.ts +19 -0
  251. package/models/ai-run.model.js +4 -0
  252. package/models/ai-run.model.js.map +1 -0
  253. package/models/ai-terminal-conversation.model.d.ts +17 -0
  254. package/models/ai-terminal-conversation.model.js +4 -0
  255. package/models/ai-terminal-conversation.model.js.map +1 -0
  256. package/models/ai-terminal-issue-report.model.d.ts +19 -0
  257. package/models/ai-terminal-issue-report.model.js +4 -0
  258. package/models/ai-terminal-issue-report.model.js.map +1 -0
  259. package/models/ai-terminal-message.model.d.ts +22 -0
  260. package/models/ai-terminal-message.model.js +4 -0
  261. package/models/ai-terminal-message.model.js.map +1 -0
  262. package/models/app-setting.model.d.ts +16 -0
  263. package/models/app-setting.model.js +4 -0
  264. package/models/app-setting.model.js.map +1 -0
  265. package/{src/models/app-status.model.ts → models/app-status.model.d.ts} +2 -3
  266. package/models/app-status.model.js +4 -0
  267. package/models/app-status.model.js.map +1 -0
  268. package/{src/models/billing-logged-in-users.model.ts → models/billing-logged-in-users.model.d.ts} +4 -5
  269. package/models/billing-logged-in-users.model.js +4 -0
  270. package/models/billing-logged-in-users.model.js.map +1 -0
  271. package/models/collection-document.model.d.ts +21 -0
  272. package/models/collection-document.model.js +4 -0
  273. package/models/collection-document.model.js.map +1 -0
  274. package/models/communication-metric.model.d.ts +20 -0
  275. package/models/communication-metric.model.js +4 -0
  276. package/models/communication-metric.model.js.map +1 -0
  277. package/{src/models/counter.model.ts → models/counter.model.d.ts} +3 -4
  278. package/models/counter.model.js +4 -0
  279. package/models/counter.model.js.map +1 -0
  280. package/models/cron-job-history.model.d.ts +15 -0
  281. package/models/cron-job-history.model.js +4 -0
  282. package/models/cron-job-history.model.js.map +1 -0
  283. package/models/cron-job.model.d.ts +14 -0
  284. package/models/cron-job.model.js +4 -0
  285. package/models/cron-job.model.js.map +1 -0
  286. package/models/customer-notification.model.d.ts +26 -0
  287. package/models/customer-notification.model.js +4 -0
  288. package/models/customer-notification.model.js.map +1 -0
  289. package/models/customer-portal-password.model.d.ts +11 -0
  290. package/models/customer-portal-password.model.js +4 -0
  291. package/models/customer-portal-password.model.js.map +1 -0
  292. package/models/dialog.model.d.ts +23 -0
  293. package/models/dialog.model.js +4 -0
  294. package/models/dialog.model.js.map +1 -0
  295. package/models/email-history.model.d.ts +32 -0
  296. package/{src/models/email-history.model.ts → models/email-history.model.js} +4 -36
  297. package/models/email-history.model.js.map +1 -0
  298. package/{src/models/email-verified.model.ts → models/email-verified.model.d.ts} +5 -6
  299. package/models/email-verified.model.js +4 -0
  300. package/models/email-verified.model.js.map +1 -0
  301. package/{src/models/file.model.ts → models/file.model.d.ts} +7 -8
  302. package/models/file.model.js +4 -0
  303. package/models/file.model.js.map +1 -0
  304. package/{src/models/flag-update.model.ts → models/flag-update.model.d.ts} +3 -4
  305. package/models/flag-update.model.js +4 -0
  306. package/models/flag-update.model.js.map +1 -0
  307. package/{src/models/flag.model.ts → models/flag.model.d.ts} +3 -4
  308. package/models/flag.model.js +4 -0
  309. package/models/flag.model.js.map +1 -0
  310. package/models/log-method-latency.model.d.ts +10 -0
  311. package/models/log-method-latency.model.js +4 -0
  312. package/models/log-method-latency.model.js.map +1 -0
  313. package/{src/models/log-subscription.model.ts → models/log-subscription.model.d.ts} +9 -11
  314. package/models/log-subscription.model.js +4 -0
  315. package/models/log-subscription.model.js.map +1 -0
  316. package/models/log.model.d.ts +17 -0
  317. package/models/log.model.js +4 -0
  318. package/models/log.model.js.map +1 -0
  319. package/{src/models/logged-in-users.model.ts → models/logged-in-users.model.d.ts} +5 -6
  320. package/models/logged-in-users.model.js +4 -0
  321. package/models/logged-in-users.model.js.map +1 -0
  322. package/{src/models/method-response.model.ts → models/method-response.model.d.ts} +6 -7
  323. package/models/method-response.model.js +4 -0
  324. package/models/method-response.model.js.map +1 -0
  325. package/models/method.model.d.ts +26 -0
  326. package/models/method.model.js +4 -0
  327. package/models/method.model.js.map +1 -0
  328. package/{src/models/monitor-cpu.model.ts → models/monitor-cpu.model.d.ts} +7 -9
  329. package/models/monitor-cpu.model.js +4 -0
  330. package/models/monitor-cpu.model.js.map +1 -0
  331. package/models/monitor-function.model.d.ts +14 -0
  332. package/models/monitor-function.model.js +4 -0
  333. package/models/monitor-function.model.js.map +1 -0
  334. package/models/monitor-memory.model.d.ts +15 -0
  335. package/models/monitor-memory.model.js +4 -0
  336. package/models/monitor-memory.model.js.map +1 -0
  337. package/models/monitor-mongo.model.d.ts +13 -0
  338. package/models/monitor-mongo.model.js +4 -0
  339. package/models/monitor-mongo.model.js.map +1 -0
  340. package/{src/models/notification.model.ts → models/notification.model.d.ts} +4 -6
  341. package/models/notification.model.js +4 -0
  342. package/models/notification.model.js.map +1 -0
  343. package/models/openai-usage-ledger.model.d.ts +30 -0
  344. package/models/openai-usage-ledger.model.js +4 -0
  345. package/models/openai-usage-ledger.model.js.map +1 -0
  346. package/models/pagination.model.d.ts +11 -0
  347. package/models/pagination.model.js +28 -0
  348. package/models/pagination.model.js.map +1 -0
  349. package/models/permission.model.d.ts +12 -0
  350. package/models/permission.model.js +4 -0
  351. package/models/permission.model.js.map +1 -0
  352. package/models/report-builder-dashboard-builder.model.d.ts +25 -0
  353. package/models/report-builder-dashboard-builder.model.js +4 -0
  354. package/models/report-builder-dashboard-builder.model.js.map +1 -0
  355. package/models/report-builder-library.model.d.ts +17 -0
  356. package/models/report-builder-library.model.js +4 -0
  357. package/models/report-builder-library.model.js.map +1 -0
  358. package/models/report-builder-report.model.d.ts +121 -0
  359. package/models/report-builder-report.model.js +4 -0
  360. package/models/report-builder-report.model.js.map +1 -0
  361. package/models/report-builder.model.d.ts +61 -0
  362. package/models/report-builder.model.js +4 -0
  363. package/models/report-builder.model.js.map +1 -0
  364. package/models/select-data-label.model.d.ts +9 -0
  365. package/models/select-data-label.model.js +4 -0
  366. package/models/select-data-label.model.js.map +1 -0
  367. package/models/server-message.model.d.ts +32 -0
  368. package/models/server-message.model.js +4 -0
  369. package/models/server-message.model.js.map +1 -0
  370. package/models/slow-query-report.model.d.ts +23 -0
  371. package/models/slow-query-report.model.js +4 -0
  372. package/models/slow-query-report.model.js.map +1 -0
  373. package/models/subscription.model.d.ts +31 -0
  374. package/models/subscription.model.js +4 -0
  375. package/models/subscription.model.js.map +1 -0
  376. package/models/support-ticket.model.d.ts +87 -0
  377. package/models/support-ticket.model.js +4 -0
  378. package/models/support-ticket.model.js.map +1 -0
  379. package/models/user-group.model.d.ts +20 -0
  380. package/models/user-group.model.js +4 -0
  381. package/models/user-group.model.js.map +1 -0
  382. package/{src/models/user-guide.model.ts → models/user-guide.model.d.ts} +4 -5
  383. package/models/user-guide.model.js +4 -0
  384. package/models/user-guide.model.js.map +1 -0
  385. package/models/user.model.d.ts +84 -0
  386. package/models/user.model.js +4 -0
  387. package/models/user.model.js.map +1 -0
  388. package/package.json +1 -1
  389. package/private/images/ResolveIO.png +0 -0
  390. package/public_api.js +127 -0
  391. package/public_api.js.map +1 -0
  392. package/publications/ai-terminal.d.ts +1 -0
  393. package/publications/ai-terminal.js +122 -0
  394. package/publications/ai-terminal.js.map +1 -0
  395. package/publications/app-settings.d.ts +2 -0
  396. package/publications/app-settings.js +28 -0
  397. package/publications/app-settings.js.map +1 -0
  398. package/publications/app-status.d.ts +2 -0
  399. package/publications/app-status.js +16 -0
  400. package/publications/app-status.js.map +1 -0
  401. package/publications/cron-jobs.d.ts +2 -0
  402. package/publications/cron-jobs.js +88 -0
  403. package/publications/cron-jobs.js.map +1 -0
  404. package/publications/customer-notifications.d.ts +2 -0
  405. package/publications/customer-notifications.js +161 -0
  406. package/publications/customer-notifications.js.map +1 -0
  407. package/publications/files.d.ts +2 -0
  408. package/publications/files.js +36 -0
  409. package/publications/files.js.map +1 -0
  410. package/publications/flags-update.d.ts +2 -0
  411. package/publications/flags-update.js +22 -0
  412. package/publications/flags-update.js.map +1 -0
  413. package/publications/flags.d.ts +2 -0
  414. package/publications/flags.js +22 -0
  415. package/publications/flags.js.map +1 -0
  416. package/publications/logs.d.ts +2 -0
  417. package/publications/logs.js +164 -0
  418. package/publications/logs.js.map +1 -0
  419. package/publications/notifications.d.ts +2 -0
  420. package/publications/notifications.js +16 -0
  421. package/publications/notifications.js.map +1 -0
  422. package/publications/report-builder-dashboard-builders.d.ts +2 -0
  423. package/publications/report-builder-dashboard-builders.js +42 -0
  424. package/publications/report-builder-dashboard-builders.js.map +1 -0
  425. package/publications/report-builder-libraries.d.ts +2 -0
  426. package/publications/report-builder-libraries.js +90 -0
  427. package/publications/report-builder-libraries.js.map +1 -0
  428. package/publications/report-builder-reports.d.ts +2 -0
  429. package/publications/report-builder-reports.js +50 -0
  430. package/publications/report-builder-reports.js.map +1 -0
  431. package/publications/super-admin.d.ts +2 -0
  432. package/publications/super-admin.js +16 -0
  433. package/publications/super-admin.js.map +1 -0
  434. package/publications/user-groups.d.ts +1 -0
  435. package/publications/user-groups.js +16 -0
  436. package/publications/user-groups.js.map +1 -0
  437. package/publications/user-guides.d.ts +1 -0
  438. package/publications/user-guides.js +16 -0
  439. package/publications/user-guides.js.map +1 -0
  440. package/resolveio-server-app.d.ts +70 -0
  441. package/resolveio-server-app.js +801 -0
  442. package/resolveio-server-app.js.map +1 -0
  443. package/server-app.d.ts +228 -0
  444. package/server-app.js +3566 -0
  445. package/server-app.js.map +1 -0
  446. package/services/codex-client.d.ts +128 -0
  447. package/services/codex-client.js +1629 -0
  448. package/services/codex-client.js.map +1 -0
  449. package/services/openai-client.d.ts +46 -0
  450. package/services/openai-client.js +318 -0
  451. package/services/openai-client.js.map +1 -0
  452. package/types/error-report.d.ts +25 -0
  453. package/types/error-report.js +4 -0
  454. package/types/error-report.js.map +1 -0
  455. package/types/slow-query-report.d.ts +27 -0
  456. package/types/slow-query-report.js +6 -0
  457. package/types/slow-query-report.js.map +1 -0
  458. package/util/ai-qa-policy.d.ts +124 -0
  459. package/util/ai-qa-policy.js +736 -0
  460. package/util/ai-qa-policy.js.map +1 -0
  461. package/util/ai-run-evidence-adapters.d.ts +109 -0
  462. package/util/ai-run-evidence-adapters.js +7234 -0
  463. package/util/ai-run-evidence-adapters.js.map +1 -0
  464. package/util/ai-run-evidence-dashboard.d.ts +88 -0
  465. package/util/ai-run-evidence-dashboard.js +343 -0
  466. package/util/ai-run-evidence-dashboard.js.map +1 -0
  467. package/util/ai-run-evidence-eval.d.ts +86 -0
  468. package/util/ai-run-evidence-eval.js +1018 -0
  469. package/util/ai-run-evidence-eval.js.map +1 -0
  470. package/util/ai-run-evidence.d.ts +244 -0
  471. package/util/ai-run-evidence.js +1096 -0
  472. package/util/ai-run-evidence.js.map +1 -0
  473. package/util/ai-runner-artifacts.d.ts +82 -0
  474. package/util/ai-runner-artifacts.js +713 -0
  475. package/util/ai-runner-artifacts.js.map +1 -0
  476. package/util/ai-runner-manager-autopilot.d.ts +210 -0
  477. package/util/ai-runner-manager-autopilot.js +642 -0
  478. package/util/ai-runner-manager-autopilot.js.map +1 -0
  479. package/util/ai-runner-manager-policy.d.ts +807 -0
  480. package/util/ai-runner-manager-policy.js +3501 -0
  481. package/util/ai-runner-manager-policy.js.map +1 -0
  482. package/util/ai-runner-qa-auth.d.ts +5 -0
  483. package/util/ai-runner-qa-auth.js +839 -0
  484. package/util/ai-runner-qa-auth.js.map +1 -0
  485. package/util/ai-runner-qa-tools.d.ts +26 -0
  486. package/util/ai-runner-qa-tools.js +3520 -0
  487. package/util/ai-runner-qa-tools.js.map +1 -0
  488. package/util/aicoder-runner-v6.d.ts +426 -0
  489. package/util/aicoder-runner-v6.js +2464 -0
  490. package/util/aicoder-runner-v6.js.map +1 -0
  491. package/util/common.d.ts +31 -0
  492. package/util/common.js +683 -0
  493. package/util/common.js.map +1 -0
  494. package/util/customer-portal-password.d.ts +13 -0
  495. package/util/customer-portal-password.js +209 -0
  496. package/util/customer-portal-password.js.map +1 -0
  497. package/util/error-reporter.d.ts +52 -0
  498. package/util/error-reporter.js +326 -0
  499. package/util/error-reporter.js.map +1 -0
  500. package/util/error-tracking.d.ts +13 -0
  501. package/util/error-tracking.js +120 -0
  502. package/util/error-tracking.js.map +1 -0
  503. package/util/openai-usage-cost.d.ts +6 -0
  504. package/util/openai-usage-cost.js +103 -0
  505. package/util/openai-usage-cost.js.map +1 -0
  506. package/util/report-builder-unwinds.d.ts +15 -0
  507. package/util/report-builder-unwinds.js +156 -0
  508. package/util/report-builder-unwinds.js.map +1 -0
  509. package/util/runner-process-janitor.d.ts +27 -0
  510. package/util/runner-process-janitor.js +208 -0
  511. package/util/runner-process-janitor.js.map +1 -0
  512. package/util/schema-report-builder.d.ts +6 -0
  513. package/util/schema-report-builder.js +481 -0
  514. package/util/schema-report-builder.js.map +1 -0
  515. package/util/slow-query-reporter.d.ts +28 -0
  516. package/util/slow-query-reporter.js +226 -0
  517. package/util/slow-query-reporter.js.map +1 -0
  518. package/util/subscription-dependency-context.d.ts +34 -0
  519. package/util/subscription-dependency-context.js +1283 -0
  520. package/util/subscription-dependency-context.js.map +1 -0
  521. package/util/support-runner-v5.d.ts +1426 -0
  522. package/util/support-runner-v5.js +7647 -0
  523. package/util/support-runner-v5.js.map +1 -0
  524. package/util/tokenizer.d.ts +5 -0
  525. package/util/tokenizer.js +41 -0
  526. package/util/tokenizer.js.map +1 -0
  527. package/workers/codex-runner.worker.d.ts +1 -0
  528. package/workers/codex-runner.worker.js +192 -0
  529. package/workers/codex-runner.worker.js.map +1 -0
  530. package/.nodemon.json +0 -5
  531. package/.vscode/settings.json +0 -21
  532. package/AGENTS.md +0 -195
  533. package/README.md +0 -22
  534. package/build_package.sh +0 -5
  535. package/compileDTS.pl +0 -64
  536. package/docs/ai-assistant-nightly-eval.md +0 -65
  537. package/docs/ai-assistant-preflight-checklist.md +0 -23
  538. package/docs/ai-assistant-report-builder-bridge-playbook.md +0 -115
  539. package/eslint-plugin-custom/index.js +0 -7
  540. package/eslint-plugin-custom/rules/no-filter-zero-index.js +0 -44
  541. package/eslint.config.js +0 -103
  542. package/gulpfile.js +0 -216
  543. package/methodAndPublicationListGenerator.py +0 -375
  544. package/mongodbensurers.js +0 -2
  545. package/mongostop.js +0 -3
  546. package/scripts/cleanup-bypassed-callmethod-logs.js +0 -616
  547. package/settings.development.json +0 -25
  548. package/settings.development.redacted.json +0 -25
  549. package/src/.env +0 -12
  550. package/src/ai/assistant-core-heuristics.ts +0 -379
  551. package/src/ai/resolveio-platform-intelligence-memory-corpus.ts +0 -185
  552. package/src/ai/resolveio-platform-intelligence-memory.ts +0 -325
  553. package/src/ai/resolveio-platform-intelligence.ts +0 -462
  554. package/src/client-server-app.ts +0 -12
  555. package/src/collections/ai-run.collection.ts +0 -117
  556. package/src/collections/ai-terminal-conversation.collection.ts +0 -91
  557. package/src/collections/ai-terminal-issue-report.collection.ts +0 -99
  558. package/src/collections/ai-terminal-message.collection.ts +0 -77
  559. package/src/collections/app-setting.collection.ts +0 -104
  560. package/src/collections/app-status.collection.ts +0 -58
  561. package/src/collections/communication-metric.collection.ts +0 -84
  562. package/src/collections/counter.collection.ts +0 -56
  563. package/src/collections/cron-job-history.collection.ts +0 -94
  564. package/src/collections/cron-job.collection.ts +0 -92
  565. package/src/collections/customer-notification.collection.ts +0 -131
  566. package/src/collections/customer-portal-password.collection.ts +0 -76
  567. package/src/collections/email-history.collection.ts +0 -134
  568. package/src/collections/email-verified.collection.ts +0 -62
  569. package/src/collections/file.collection.ts +0 -74
  570. package/src/collections/flag-update.collection.ts +0 -57
  571. package/src/collections/flag.collection.ts +0 -57
  572. package/src/collections/log-method-latency.collection.ts +0 -77
  573. package/src/collections/log-subscription.collection.ts +0 -80
  574. package/src/collections/log.collection.ts +0 -93
  575. package/src/collections/logged-in-users.collection.ts +0 -67
  576. package/src/collections/monitor-cpu.collection.ts +0 -65
  577. package/src/collections/monitor-function.collection.ts +0 -74
  578. package/src/collections/monitor-memory.collection.ts +0 -77
  579. package/src/collections/monitor-mongo.collection.ts +0 -71
  580. package/src/collections/notification.collection.ts +0 -57
  581. package/src/collections/openai-usage-ledger.collection.ts +0 -131
  582. package/src/collections/report-builder-dashboard-builder.collection.ts +0 -109
  583. package/src/collections/report-builder-library.collection.ts +0 -89
  584. package/src/collections/report-builder-report.collection.ts +0 -184
  585. package/src/collections/user-group.collection.ts +0 -89
  586. package/src/collections/user-guide.collection.ts +0 -57
  587. package/src/collections/user.collection.ts +0 -181
  588. package/src/cron/cron.ts +0 -117
  589. package/src/fixtures/cron-jobs.ts +0 -95
  590. package/src/fixtures/init.ts +0 -35
  591. package/src/http/auth.ts +0 -818
  592. package/src/http/health.ts +0 -7
  593. package/src/http/home.ts +0 -90
  594. package/src/http/slow-query-publication.ts +0 -49
  595. package/src/index.ts +0 -1
  596. package/src/managers/ai-assistant-codex-manager.manager.ts +0 -1131
  597. package/src/managers/ai-run-evidence.manager.ts +0 -264
  598. package/src/managers/communication-metric.manager.ts +0 -82
  599. package/src/managers/cron.manager.ts +0 -333
  600. package/src/managers/customer-notification-content.manager.ts +0 -236
  601. package/src/managers/diagnostic-manager-bootstrap.ts +0 -165
  602. package/src/managers/error-auto-fix.manager.ts +0 -2767
  603. package/src/managers/local-log.manager.ts +0 -113
  604. package/src/managers/method.manager.ts +0 -1857
  605. package/src/managers/mongo.manager.ts +0 -4575
  606. package/src/managers/monitor.manager.ts +0 -507
  607. package/src/managers/openai-usage-ledger.manager.ts +0 -112
  608. package/src/managers/slow-query-verifier.manager.ts +0 -3590
  609. package/src/managers/slow-query.manager.ts +0 -519
  610. package/src/managers/subscription.manager.ts +0 -3128
  611. package/src/managers/websocket.manager.ts +0 -746
  612. package/src/managers/worker-dispatcher.manager.ts +0 -1360
  613. package/src/managers/worker-server.manager.ts +0 -536
  614. package/src/methods/accounts.ts +0 -532
  615. package/src/methods/ai-terminal.ts +0 -29070
  616. package/src/methods/app-settings.ts +0 -114
  617. package/src/methods/aws.ts +0 -649
  618. package/src/methods/collections.ts +0 -641
  619. package/src/methods/counters.ts +0 -69
  620. package/src/methods/cron-jobs.ts +0 -2614
  621. package/src/methods/customer-notifications.ts +0 -458
  622. package/src/methods/diagnostics.ts +0 -616
  623. package/src/methods/flag-updates.ts +0 -7
  624. package/src/methods/flags.ts +0 -7
  625. package/src/methods/logs.ts +0 -657
  626. package/src/methods/mongo-explorer.ts +0 -1880
  627. package/src/methods/monitor.ts +0 -540
  628. package/src/methods/pdf.ts +0 -1236
  629. package/src/methods/publications.ts +0 -129
  630. package/src/methods/report-builder.ts +0 -3300
  631. package/src/methods/support.ts +0 -335
  632. package/src/models/ai-run.model.ts +0 -27
  633. package/src/models/ai-terminal-conversation.model.ts +0 -19
  634. package/src/models/ai-terminal-issue-report.model.ts +0 -21
  635. package/src/models/ai-terminal-message.model.ts +0 -24
  636. package/src/models/app-setting.model.ts +0 -17
  637. package/src/models/collection-document.model.ts +0 -24
  638. package/src/models/communication-metric.model.ts +0 -23
  639. package/src/models/cron-job-history.model.ts +0 -16
  640. package/src/models/cron-job.model.ts +0 -15
  641. package/src/models/customer-notification.model.ts +0 -28
  642. package/src/models/customer-portal-password.model.ts +0 -12
  643. package/src/models/dialog.model.ts +0 -25
  644. package/src/models/log-method-latency.model.ts +0 -11
  645. package/src/models/log.model.ts +0 -19
  646. package/src/models/method.model.ts +0 -25
  647. package/src/models/monitor-function.model.ts +0 -16
  648. package/src/models/monitor-memory.model.ts +0 -17
  649. package/src/models/monitor-mongo.model.ts +0 -15
  650. package/src/models/openai-usage-ledger.model.ts +0 -56
  651. package/src/models/pagination.model.ts +0 -35
  652. package/src/models/permission.model.ts +0 -14
  653. package/src/models/report-builder-dashboard-builder.model.ts +0 -29
  654. package/src/models/report-builder-library.model.ts +0 -20
  655. package/src/models/report-builder-report.model.ts +0 -136
  656. package/src/models/report-builder.model.ts +0 -68
  657. package/src/models/select-data-label.model.ts +0 -9
  658. package/src/models/server-message.model.ts +0 -31
  659. package/src/models/slow-query-report.model.ts +0 -23
  660. package/src/models/subscription.model.ts +0 -73
  661. package/src/models/support-ticket.model.ts +0 -104
  662. package/src/models/user-group.model.ts +0 -24
  663. package/src/models/user.model.ts +0 -96
  664. package/src/private/images/ResolveIO.png +0 -0
  665. package/src/publications/ai-terminal.ts +0 -73
  666. package/src/publications/app-settings.ts +0 -25
  667. package/src/publications/app-status.ts +0 -13
  668. package/src/publications/cron-jobs.ts +0 -40
  669. package/src/publications/customer-notifications.ts +0 -101
  670. package/src/publications/files.ts +0 -33
  671. package/src/publications/flags-update.ts +0 -19
  672. package/src/publications/flags.ts +0 -19
  673. package/src/publications/logs.ts +0 -163
  674. package/src/publications/notifications.ts +0 -13
  675. package/src/publications/report-builder-dashboard-builders.ts +0 -39
  676. package/src/publications/report-builder-libraries.ts +0 -41
  677. package/src/publications/report-builder-reports.ts +0 -47
  678. package/src/publications/super-admin.ts +0 -13
  679. package/src/publications/user-groups.ts +0 -12
  680. package/src/publications/user-guides.ts +0 -12
  681. package/src/resolveio-server-app.ts +0 -617
  682. package/src/server-app.ts +0 -3354
  683. package/src/services/codex-client.ts +0 -1231
  684. package/src/services/openai-client.ts +0 -265
  685. package/src/types/error-report.ts +0 -26
  686. package/src/types/js-tiktoken.d.ts +0 -11
  687. package/src/types/slow-query-report.ts +0 -28
  688. package/src/util/ai-qa-policy.ts +0 -925
  689. package/src/util/ai-run-evidence-adapters.ts +0 -8347
  690. package/src/util/ai-run-evidence-dashboard.ts +0 -323
  691. package/src/util/ai-run-evidence-eval.ts +0 -1057
  692. package/src/util/ai-run-evidence.ts +0 -1430
  693. package/src/util/ai-runner-artifacts.ts +0 -586
  694. package/src/util/ai-runner-manager-autopilot.ts +0 -961
  695. package/src/util/ai-runner-manager-policy.ts +0 -5011
  696. package/src/util/ai-runner-qa-auth.ts +0 -838
  697. package/src/util/ai-runner-qa-tools.ts +0 -3536
  698. package/src/util/aicoder-runner-v6.ts +0 -3121
  699. package/src/util/common.ts +0 -649
  700. package/src/util/customer-portal-password.ts +0 -183
  701. package/src/util/error-reporter.ts +0 -332
  702. package/src/util/error-tracking.ts +0 -79
  703. package/src/util/openai-usage-cost.ts +0 -114
  704. package/src/util/report-builder-unwinds.ts +0 -180
  705. package/src/util/runner-process-janitor.ts +0 -219
  706. package/src/util/schema-report-builder.ts +0 -448
  707. package/src/util/slow-query-reporter.ts +0 -216
  708. package/src/util/subscription-dependency-context.ts +0 -1096
  709. package/src/util/support-runner-v5.ts +0 -10040
  710. package/src/util/tokenizer.ts +0 -38
  711. package/src/workers/codex-runner.worker.ts +0 -142
  712. package/start_server.sh +0 -5
  713. package/tests/ai-assistant-corpus-build.ts +0 -484
  714. package/tests/ai-assistant-corpus-replay-e2e.ts +0 -774
  715. package/tests/ai-assistant-data-parity-e2e.ts +0 -1989
  716. package/tests/ai-assistant-eval-triage.ts +0 -831
  717. package/tests/ai-assistant-openai-e2e.ts +0 -1061
  718. package/tests/ai-assistant-openai-git-e2e.ts +0 -155
  719. package/tests/ai-assistant-preflight-matrix.ts +0 -215
  720. package/tests/ai-assistant-routing-eval.test.ts +0 -585
  721. package/tests/ai-assistant-snf-live-eval.ts +0 -975
  722. package/tests/ai-assistant-utils.test.ts +0 -4834
  723. package/tests/ai-manager-autopilot-snapshot.test.ts +0 -193
  724. package/tests/ai-manager-recovery-checkpoint.test.ts +0 -1383
  725. package/tests/ai-run-eval.test.ts +0 -132
  726. package/tests/ai-run-evidence.test.ts +0 -3773
  727. package/tests/ai-runner-contract.test.ts +0 -515
  728. package/tests/aicoder-runner-v6.test.ts +0 -822
  729. package/tests/error-reporter.test.ts +0 -145
  730. package/tests/method-publication-generator.test.ts +0 -46
  731. package/tests/report-builder-linking.test.ts +0 -79
  732. package/tests/resolveio-platform-intelligence.test.ts +0 -352
  733. package/tests/server-app-cron-owner.test.ts +0 -127
  734. package/tests/subscription-connect-race.test.ts +0 -158
  735. package/tests/subscription-dependency-context.test.ts +0 -324
  736. package/tests/subscription-manager-collection-tracking.test.ts +0 -86
  737. package/tests/subscription-manager-invalidation.test.ts +0 -86
  738. package/tests/support-runner-v5.test.ts +0 -3201
  739. package/tsconfig.json +0 -34
  740. /package/{src/private → private}/email-templates/enrollment.html +0 -0
  741. /package/{src/private → private}/email-templates/forgot-password.html +0 -0
  742. /package/{src/private → private}/email-templates/support-ticket-deleted.html +0 -0
  743. /package/{src/private → private}/email-templates/support-ticket-modified.html +0 -0
  744. /package/{src/private → private}/email-templates/support-ticket.html +0 -0
  745. /package/{src/public_api.ts → public_api.d.ts} +0 -0
@@ -1,3128 +0,0 @@
1
- import { ChangeStream, ChangeStreamDeleteDocument, ChangeStreamInsertDocument, ChangeStreamReplaceDocument, ChangeStreamUpdateDocument, ResumeToken } from 'mongodb';
2
- import * as NodeCache from 'node-cache';
3
- import { pack, unpack } from 'msgpackr';
4
- import * as WebSocket from 'ws';
5
- import { Flags } from '../collections/flag.collection';
6
- import { LoggedInUsers } from '../collections/logged-in-users.collection';
7
- import { LoggedInUserModel } from '../models/logged-in-users.model';
8
- import { ServerResponseModel } from '../models/server-message.model';
9
- import { ActiveSubscriptionClientModel, ActiveSubscriptionModel, SubscriptionModel, SubscriptionPubModel } from '../models/subscription.model';
10
- import { loadAppStatusPublications } from '../publications/app-status';
11
- import { loadAppSettingsPublications } from '../publications/app-settings';
12
- import { loadAiTerminalPublications } from '../publications/ai-terminal';
13
- import { loadCronJobPublications } from '../publications/cron-jobs';
14
- import { loadCustomerNotificationPublications } from '../publications/customer-notifications';
15
- import { loadFilePublications } from '../publications/files';
16
- import { loadFlagsPublications } from '../publications/flags';
17
- import { loadFlagsUpdatePublications } from '../publications/flags-update';
18
- import { loadLogPublications } from '../publications/logs';
19
- import { loadNotificationPublications } from '../publications/notifications';
20
- import { loadReportBuilderDashboardBuilderPublications } from '../publications/report-builder-dashboard-builders';
21
- import { loadReportBuilderLibraryPublications } from '../publications/report-builder-libraries';
22
- import { loadReportBuilderReportPublications } from '../publications/report-builder-reports';
23
- import { loadSuperAdminPublications } from '../publications/super-admin';
24
- import { loadUserGroupPublications } from '../publications/user-groups';
25
- import { loadUserGuidePublications } from '../publications/user-guides';
26
- import { ResolveIOServer } from '../resolveio-server-app';
27
- import { deepCopy, objectIdHexString, round } from '../util/common';
28
- import { ErrorReporter } from '../util/error-reporter';
29
- import { ensureErrorWithCorrelation } from '../util/error-tracking';
30
- import { DependencyContextSnapshot, PublicationContext, QueryMeta, deserializeDependencySnapshot, withDependencyTracking } from '../util/subscription-dependency-context';
31
- import { MonitorManagerFunction } from './monitor.manager';
32
- import { WebSocketManager } from './websocket.manager';
33
- const v8 = require('v8');
34
- const sift = require('sift');
35
-
36
- function resolveHeapLimitBytes(): number {
37
- const stats = v8.getHeapStatistics();
38
- if (stats && typeof stats.heap_size_limit === 'number') {
39
- return stats.heap_size_limit;
40
- }
41
-
42
- if (stats && typeof stats.total_available_size === 'number') {
43
- return stats.total_available_size;
44
- }
45
-
46
- return 0;
47
- }
48
-
49
- // Performance Dependencies
50
- // import * as path from 'path';
51
- // import { Worker } from 'worker_threads';
52
-
53
- interface MongoQueueModel {
54
- _id: number,
55
- type: string;
56
- collection: string;
57
- subscription: ActiveSubscriptionModel;
58
- running: boolean;
59
- run_again: boolean;
60
- }
61
-
62
- interface OplogResumeTokenDocument {
63
- _id: string;
64
- token?: ResumeToken;
65
- updatedAt?: Date;
66
- }
67
-
68
- interface SubscriptionInvalidationEvent {
69
- type: string;
70
- documentId?: any;
71
- document?: any;
72
- }
73
-
74
- interface PublicationExecution {
75
- result?: any;
76
- packedResult?: Uint8Array | Buffer;
77
- snapshot?: DependencyContextSnapshot;
78
- encoding?: 'msgpack' | 'json';
79
- workerUsed?: boolean;
80
- }
81
-
82
- // interface CurrentPerformanceMonitor {
83
- // _id: number;
84
- // function: string;
85
- // publication: string;
86
- // subscriptionData: any[];
87
- // date_start: Date;
88
- // date_end: Date;
89
- // duration: number;
90
- // result: string;
91
- // }
92
-
93
- export class SubscriptionManager {
94
- private _websocketManager: WebSocketManager;
95
- private _publications: SubscriptionModel = {};
96
- private _subscriptions: ActiveSubscriptionModel[] = [];
97
- private _wss: WebSocket.Server;
98
- private _loggedInUsers: LoggedInUserModel[] = [];
99
- private _lastRouteBySocket = new Map<string, { route: string, dateMs: number }>();
100
-
101
- private _mongoQueue: MongoQueueModel[] = [];
102
- private _mongoQueueId = 0;
103
-
104
- private _oplog$: ChangeStream;
105
- private _oplogMode: 'auto' | 'change-stream' | 'local' = 'auto';
106
- private _useLocalOplog = false;
107
- private _localOplogResyncIntervalMs = 0;
108
- private _localOplogResyncTimer: NodeJS.Timeout = null;
109
-
110
- private _nodeCache;
111
- private _cacheId = 1;
112
-
113
- private _heapSize = resolveHeapLimitBytes();
114
- private _heapLimit: number;
115
-
116
- private serverConfig;
117
-
118
- private _monitorManagerFunction: MonitorManagerFunction;
119
-
120
- private _enableDebug = false;
121
- private _enableDependencyDebug = false;
122
- private _connectDebug = false;
123
- private _debugOplogCollections = [];
124
- private _debugOplogHits = 0;
125
- private _debugSubCollections = [];
126
- private _debugSubHits = 0;
127
- private _debugUnSubHits = 0;
128
- private _debugUnSubAllHits = 0;
129
- private _debugMongoQueueHits = 0;
130
- private _debugMongoQueueCollections = [];
131
- private _debugSendQueueHits = 0;
132
- private _debugRemoveCacheHits = 0;
133
- private _subSendDebug = false;
134
- private _subSendLogEnabled = false;
135
- private _subSendLogThresholdMs = 0;
136
- private _subSendLogBytes = 0;
137
- private _subSendLogSampleRate = 1;
138
- private _aiWorkerDebug = false;
139
- private _aiSubscriptionDebug = false;
140
- private _aiSubLastAt = new Map<string, number>();
141
-
142
- private _oplogRetryCount = 0;
143
- private _lastResumeToken: ResumeToken = null;
144
- private _lastResumeTokenSaveMs = 0;
145
- private _resumeTokenSavePromise: Promise<void> = null;
146
- private _resumeTokenSaveForcePending = false;
147
- private _fullResyncPromise: Promise<void> = null;
148
- private readonly RESUME_TOKEN_COLLECTION = 'subscription-manager-resume-tokens';
149
- private readonly RESUME_TOKEN_SAVE_INTERVAL_MS = 5000;
150
- private readonly RESUME_TOKEN_AUTO_HEAL_RETRY_THRESHOLD = 2;
151
-
152
- // Buffer to store throttled latency updates with timestamps
153
- private latencyBuffer = new Map<string, { latency: number, lastUpdate: Date }>();
154
- private _latencyFlushInProgress = false;
155
- private _latencyFlushPending = false;
156
-
157
- // Interval to flush latency updates in MongoDB
158
- private readonly LATENCY_UPDATE_INTERVAL = 60000;
159
-
160
- // Minimum time difference between two latency updates for the same user
161
- private readonly LATENCY_UPDATE_THRESHOLD_MS = 30000;
162
- private readonly LATENCY_DEBUG_THRESHOLD_MS = 1000;
163
- private readonly PUBLICATION_DEBUG_THRESHOLD_MS = 300;
164
-
165
- // private currentPerfomanceMonitor: CurrentPerformanceMonitor[] = [];
166
- // private idPerformance: number = 0;
167
- // private performanceThread;
168
-
169
- private _invalidationDebounceTimers = new Map<string, NodeJS.Timeout>();
170
- private _invalidationPendingTimestamps = new Map<string, number>();
171
- private _pendingInvalidations = new Map<string, { events: SubscriptionInvalidationEvent[] }>();
172
- private readonly DEBOUNCE_DELAY = 100; // 100ms debounce window
173
- private readonly MAX_WAIT_TIME = 500; // 500ms maximum delay
174
-
175
- private _publicationWorkerQueueLimit = 0;
176
-
177
-
178
- constructor() {}
179
-
180
- static createPublicationRegistry(serverConfig?) {
181
- const subscriptionManager = new SubscriptionManager();
182
- subscriptionManager.initializePublicationRegistry(serverConfig);
183
- return subscriptionManager;
184
- }
185
-
186
- static create(wss: WebSocket.Server, serverConfig, monitorManagerFunction: MonitorManagerFunction) {
187
- const subscriptionManager = new SubscriptionManager();
188
- setImmediate(async () => {
189
- await subscriptionManager.initialize(wss, serverConfig, monitorManagerFunction);
190
- });
191
- return subscriptionManager;
192
- }
193
-
194
- private initializePublicationRegistry(serverConfig) {
195
- this.serverConfig = serverConfig || ResolveIOServer.getServerConfig();
196
- this.registerCorePublications();
197
- }
198
-
199
- private async initialize(wss: WebSocket.Server, serverConfig, monitorManagerFunction: MonitorManagerFunction) {
200
- this._websocketManager = ResolveIOServer.getMainServer().getWebSocketManager();
201
- this._monitorManagerFunction = monitorManagerFunction;
202
-
203
- this._nodeCache = new NodeCache( { stdTTL: 0, checkperiod: 0 } );
204
-
205
- setInterval(() => this.flushThrottledLatencyUpdates(), this.LATENCY_UPDATE_INTERVAL);
206
-
207
- // setTimeout(() => {
208
- // console.log('Setting up performance thread');
209
-
210
- // this.performanceThread = new Worker(path.join(__dirname, './subscription.performance.js'));
211
-
212
- // this.performanceThread.on('exit', code => {
213
- // console.error(new Date(), 'THREAD EXITED!!!!!!!!!!!!!!!!!!', code);
214
- // });
215
-
216
- // this.performanceThread.on('error', code => {
217
- // console.error(new Date(), 'THREAD RECV ERROR !!!!!!!!!!!!!!!!!!', code);
218
- // });
219
- // }, 5000);
220
-
221
- // setInterval(() => {
222
- // console.log('Post thread msg');
223
- // this.performanceThread.postMessage(this.currentPerfomanceMonitor);
224
- // this.currentPerfomanceMonitor = [];
225
- // }, 10000);
226
-
227
- this.serverConfig = serverConfig;
228
- this._wss = wss;
229
- this._connectDebug = this.resolveConnectDebug();
230
- this._subSendDebug = this.resolveSubSendDebug();
231
- this._subSendLogEnabled = this.resolveSubSendLogEnabled();
232
- this._subSendLogThresholdMs = this.resolveSubSendLogThresholdMs();
233
- this._subSendLogBytes = this.resolveSubSendLogBytes();
234
- this._subSendLogSampleRate = this.resolveSubSendLogSampleRate();
235
- this._aiWorkerDebug = this.parseDebugFlag(process.env.AI_ASSISTANT_WORKER_DEBUG);
236
- this._aiSubscriptionDebug = this.resolveAiSubscriptionDebug();
237
-
238
- this._oplogMode = this.resolveOplogMode();
239
- this._localOplogResyncIntervalMs = this.resolveLocalOplogResyncIntervalMs();
240
-
241
- this.registerCorePublications();
242
-
243
- if (this._oplogMode === 'local') {
244
- await this.enableLocalOplogFallback('config');
245
- }
246
- else {
247
- const resumeToken = await this.loadResumeToken();
248
- await this.tailOpLog(resumeToken || undefined);
249
- }
250
-
251
- setInterval(() => {
252
- this._oplogRetryCount = 0;
253
- }, 15000);
254
-
255
- setInterval(() => {
256
- if (this.getEnableDebug()) {
257
- console.log(new Date(), 'Sub Manager', 'Subs', this._subscriptions.length);
258
- console.log(new Date(), 'Sub Manager', 'Logged In Users', this._loggedInUsers.length);
259
- console.log(new Date(), 'Sub Manager', 'Mongo Queue', this._mongoQueue.length);
260
- console.log(new Date(), 'Sub Manager', 'Mongo Queue Hits', this._debugMongoQueueHits);
261
- console.log(new Date(), 'Sub Manager', 'Mongo Queue Collections', JSON.stringify(this._debugMongoQueueCollections.sort((a, b) => a.collection.localeCompare(b.collection) || a.publication.localeCompare(b.publication)), null, 2));
262
- console.log(new Date(), 'Sub Manager', 'Oplog Hits', this._debugOplogHits);
263
- console.log(new Date(), 'Sub Manager', 'Oplog Collections', JSON.stringify(this._debugOplogCollections.sort((a, b) => a.collection.localeCompare(b.collection) || a.type.localeCompare(b.type)), null, 2));
264
- console.log(new Date(), 'Sub Manager', 'Send Queue Hits', this._debugSendQueueHits);
265
- console.log(new Date(), 'Sub Manager', 'Sub Hits', this._debugSubHits);
266
- console.log(new Date(), 'Sub Manager', 'Sub Collections', JSON.stringify(this._debugSubCollections.sort((a, b) => a.publication.localeCompare(b.publication)), null, 2));
267
- console.log(new Date(), 'Sub Manager', 'Unsub Hits', this._debugUnSubHits);
268
- console.log(new Date(), 'Sub Manager', 'Unsub All Hits', this._debugUnSubAllHits);
269
- console.log(new Date(), 'Sub Manager', 'Cache Cleanup Hits', this._debugRemoveCacheHits);
270
- }
271
-
272
- this._debugOplogHits = 0;
273
- this._debugOplogCollections = [];
274
- this._debugSubCollections = [];
275
- this._debugMongoQueueHits = 0;
276
- this._debugMongoQueueCollections = [];
277
- this._debugSendQueueHits = 0;
278
- this._debugSubHits = 0;
279
- this._debugUnSubHits = 0;
280
- this._debugUnSubAllHits = 0;
281
- this._debugRemoveCacheHits = 0;
282
- }, 60000);
283
-
284
- setInterval(async () => {
285
- this._loggedInUsers = await LoggedInUsers.find();
286
-
287
- let userCopy = deepCopy(this._loggedInUsers);
288
- for (let i = this._loggedInUsers.length - 1; i >= 0; i--) {
289
- let loggedInUser = userCopy[i];
290
-
291
- if (!loggedInUser.date || Date.now() - loggedInUser.date.getTime() > 120000) {
292
- if (this._websocketManager.getWebSocket(loggedInUser.id_ws)) {
293
- if (this.getEnableDebug()) {
294
- console.log(new Date(), 'Sub Manager', 'Unsub WS', this._websocketManager.getWebSocket(loggedInUser.id_ws)['user'], this._websocketManager.getWebSocket(loggedInUser.id_ws)['id_socket'], 2);
295
- }
296
- await this.unsubscribeAll(this._websocketManager.getWebSocket(loggedInUser.id_ws));
297
- }
298
- else {
299
- this._subscriptions.forEach(sub => {
300
- for (let j = sub.clients.length - 1; j >= 0; j--) {
301
- let client = sub.clients[j];
302
-
303
- if (client.id_socket === loggedInUser.id_ws) {
304
- sub.clients.splice(j, 1);
305
- }
306
- }
307
- });
308
-
309
- await LoggedInUsers.deleteOne({_id: loggedInUser._id});
310
-
311
- if (this._loggedInUsers.findIndex(a => a._id === loggedInUser._id) >= 0) {
312
- this._loggedInUsers.splice(this._loggedInUsers.findIndex(a => a._id === loggedInUser._id), 1);
313
- }
314
- }
315
- }
316
- }
317
-
318
- for (let i = 0; i < this._subscriptions.length; i++) {
319
- let sub = this._subscriptions[i];
320
-
321
- for (let j = sub.clients.length - 1; j >= 0; j--) {
322
- let client = sub.clients[j];
323
-
324
- if (!this._loggedInUsers.some(a => a.id_ws === client.id_socket)) {
325
- sub.clients.splice(j, 1);
326
- }
327
- }
328
- }
329
- }, 30000);
330
-
331
- let flag = await Flags.findOne({type: 'Enable Debug'});
332
- let dependencyFlag = await Flags.findOne({type: 'Enable Dependency Debug'});
333
-
334
- if (flag && flag.value) {
335
- this._enableDebug = true;
336
- }
337
- else {
338
- this._enableDebug = false;
339
- }
340
-
341
- if (dependencyFlag && dependencyFlag.value) {
342
- this._enableDependencyDebug = true;
343
- }
344
- else {
345
- this._enableDependencyDebug = process.env.ENABLE_DEPENDENCY_DEBUG === 'true';
346
- }
347
-
348
- this.setCacheLimit();
349
- this.configurePublicationWorkers();
350
- }
351
-
352
- private registerCorePublications() {
353
- loadSuperAdminPublications(this);
354
- loadAiTerminalPublications(this);
355
- loadAppStatusPublications(this);
356
- loadAppSettingsPublications(this);
357
- loadLogPublications(this);
358
- loadFilePublications(this);
359
- loadCronJobPublications(this);
360
- loadFlagsUpdatePublications(this);
361
- loadFlagsPublications(this);
362
- loadNotificationPublications(this);
363
- loadCustomerNotificationPublications(this);
364
- loadReportBuilderReportPublications(this);
365
- loadReportBuilderLibraryPublications(this);
366
- loadUserGroupPublications(this);
367
- loadUserGuidePublications(this);
368
- loadReportBuilderDashboardBuilderPublications(this);
369
- }
370
-
371
- private setCacheLimit() {
372
- if (process.env.IS_WORKERS_ENABLED === 'true') {
373
- this._heapLimit = this._heapSize * 0.4;
374
- }
375
- else {
376
- this._heapLimit = this._heapSize * 0.3; // Use 50% of total heap size
377
- }
378
- }
379
-
380
- private configurePublicationWorkers() {
381
- const config = this.serverConfig || ResolveIOServer.getServerConfig() || {};
382
- this._publicationWorkerQueueLimit = this.parsePositiveNumber(process.env.SUBSCRIPTION_WORKER_QUEUE_MAX ?? config['SUBSCRIPTION_WORKER_QUEUE_MAX']);
383
- }
384
-
385
- private parseBoolean(value: any): boolean {
386
- if (value === true) {
387
- return true;
388
- }
389
-
390
- if (value === false || value === null || value === undefined) {
391
- return false;
392
- }
393
-
394
- if (typeof value === 'number') {
395
- return value === 1;
396
- }
397
-
398
- if (typeof value === 'string') {
399
- const normalized = value.trim().toLowerCase();
400
- return ['1', 'true', 'yes', 'y', 'on'].includes(normalized);
401
- }
402
-
403
- return false;
404
- }
405
-
406
- private clampNumber(value: number, min: number, max: number): number {
407
- if (value < min) {
408
- return min;
409
- }
410
-
411
- if (value > max) {
412
- return max;
413
- }
414
-
415
- return value;
416
- }
417
-
418
- public async invalidatePubsCache(collection: string, type: string, documentId?: any, document?: any) {
419
- let queue = this._pendingInvalidations.get(collection);
420
- if (!queue) {
421
- queue = { events: [] };
422
- this._pendingInvalidations.set(collection, queue);
423
- }
424
- queue.events.push({ type, documentId, document });
425
-
426
- const debounceKey = collection;
427
- const now = Date.now();
428
-
429
- // Initialize or get existing timestamp
430
- const firstInvalidationTime = this._invalidationPendingTimestamps.get(debounceKey) || now;
431
- this._invalidationPendingTimestamps.set(debounceKey, firstInvalidationTime);
432
-
433
- // Clear any existing timer
434
- if (this._invalidationDebounceTimers.has(debounceKey)) {
435
- clearTimeout(this._invalidationDebounceTimers.get(debounceKey));
436
- }
437
-
438
- // Check if we've waited too long
439
- const waitedTooLong = (now - firstInvalidationTime) >= this.MAX_WAIT_TIME;
440
-
441
- if (waitedTooLong) {
442
- // Immediate execution path
443
- this._invalidationPendingTimestamps.delete(debounceKey);
444
- await this._executeInvalidation(collection);
445
- } else {
446
- // Normal debounce path
447
- this._invalidationDebounceTimers.set(
448
- debounceKey,
449
- setTimeout(async () => {
450
- this._invalidationPendingTimestamps.delete(debounceKey);
451
- await this._executeInvalidation(collection);
452
- }, this.DEBOUNCE_DELAY)
453
- );
454
- }
455
- }
456
-
457
- private async _executeInvalidation(collection: string) {
458
- // Clean up any existing timer (defensive)
459
- if (this._invalidationDebounceTimers.has(collection)) {
460
- clearTimeout(this._invalidationDebounceTimers.get(collection));
461
- this._invalidationDebounceTimers.delete(collection);
462
- }
463
-
464
- const queued = this._pendingInvalidations.get(collection);
465
- this._pendingInvalidations.delete(collection);
466
-
467
- if (!queued || !queued.events.length) {
468
- return;
469
- }
470
-
471
- const events = queued.events;
472
-
473
- // Original invalidation logic
474
- ResolveIOServer.getMongoManager().invalidateQueryCache(collection, true);
475
-
476
- const collSubs = this._subscriptions.filter(sub => this.subscriptionReferencesCollection(sub, collection));
477
-
478
- for (const sub of collSubs) {
479
- if (this._enableDebug) {
480
- console.log(new Date(), 'Invalidate Sub', sub.publication, sub.running, sub.runAgain);
481
- }
482
-
483
- if (!(await this.shouldInvalidateSubscriptionForEvents(sub, collection, events))) {
484
- continue;
485
- }
486
-
487
- if (sub.running) {
488
- sub.runAgain = true;
489
- this.dependencyDebug('Subscription busy, scheduling rerun', { publication: sub.publication, collection, events: this.summarizeEvents(events) });
490
- continue;
491
- }
492
-
493
- const batchType = events.length === 1 ? events[0].type : events.map(event => event.type).join(',');
494
-
495
- if (this._publications[sub.publication].user_specific) {
496
- this.dependencyDebug('Triggering user-specific invalidation', { publication: sub.publication, collection, events: this.summarizeEvents(events), clientCount: sub.clients.length });
497
- for (let client of sub.clients) {
498
- const ws = this._websocketManager.getWebSocket(client.id_socket);
499
- if (ws?.readyState === WebSocket.OPEN) {
500
- try {
501
- await this.sendDataToOneWithRetry(ws, client.messageId, sub, collection, batchType);
502
- }
503
- catch {
504
- // Error handling
505
- }
506
- }
507
- }
508
- }
509
- else {
510
- this.dependencyDebug('Triggering broadcast invalidation', { publication: sub.publication, collection, events: this.summarizeEvents(events), clientCount: sub.clients.length });
511
- await this.sendDataToAllWithRetry(sub, collection, batchType);
512
- }
513
- }
514
- }
515
-
516
- private subscriptionReferencesCollection(sub: ActiveSubscriptionModel, collection: string): boolean {
517
- if (sub.collections?.includes(collection)) {
518
- return true;
519
- }
520
-
521
- this.ensureDependencyContainers(sub);
522
-
523
- const dependencyIds = sub.collectionDependencies?.get(collection);
524
- if (dependencyIds && dependencyIds.size > 0) {
525
- return true;
526
- }
527
-
528
- const filters = sub.collectionFilters?.get(collection);
529
- if (filters && filters.length > 0) {
530
- return true;
531
- }
532
-
533
- if (sub.watchAllCollections?.has(collection)) {
534
- return true;
535
- }
536
-
537
- return false;
538
- }
539
-
540
- private async delay(ms: number) {
541
- // eslint-disable-next-line no-restricted-syntax
542
- return new Promise(resolve => setTimeout(resolve, ms));
543
- }
544
-
545
- private getMessageDateMs(messageDate: any): number {
546
- if (messageDate instanceof Date && !isNaN(messageDate.getTime())) {
547
- return messageDate.getTime();
548
- }
549
-
550
- if (typeof messageDate === 'string' || typeof messageDate === 'number') {
551
- const parsed = new Date(messageDate);
552
- if (!isNaN(parsed.getTime())) {
553
- return parsed.getTime();
554
- }
555
- }
556
-
557
- return Date.now();
558
- }
559
-
560
- private isStaleRouteMessage(ws: WebSocket, messageRoute: string, messageDate: any): boolean {
561
- const socketId = ws ? ws['id_socket'] : '';
562
- if (!socketId) {
563
- return false;
564
- }
565
-
566
- if (!messageRoute || messageRoute === 'Bypass') {
567
- return false;
568
- }
569
-
570
- const messageDateMs = this.getMessageDateMs(messageDate);
571
- const lastRoute = this._lastRouteBySocket.get(socketId);
572
-
573
- if (lastRoute && messageDateMs < lastRoute.dateMs) {
574
- this.dependencyDebug('Skip route cleanup due to stale route message', {
575
- socketId,
576
- messageRoute,
577
- messageDateMs,
578
- lastRoute: lastRoute.route,
579
- lastRouteDateMs: lastRoute.dateMs
580
- });
581
- return true;
582
- }
583
-
584
- this._lastRouteBySocket.set(socketId, { route: messageRoute, dateMs: messageDateMs });
585
- return false;
586
- }
587
-
588
- private getPublicationWorkerDispatcher() {
589
- const mainServer = ResolveIOServer.getMainServer();
590
- return mainServer && typeof mainServer.getWorkerDispatcherManager === 'function'
591
- ? mainServer.getWorkerDispatcherManager()
592
- : null;
593
- }
594
-
595
- private shouldUsePublicationWorkers(): boolean {
596
- const dispatcher = this.getPublicationWorkerDispatcher();
597
- if (!dispatcher || !dispatcher.hasWorkers()) {
598
- return false;
599
- }
600
- if (typeof dispatcher.hasWorkersForMethod === 'function' && !dispatcher.hasWorkersForMethod('runPublication')) {
601
- return false;
602
- }
603
-
604
- if (this._publicationWorkerQueueLimit > 0) {
605
- const snapshot = dispatcher.getQueueSnapshot();
606
- if (snapshot.queueDepth >= this._publicationWorkerQueueLimit) {
607
- return false;
608
- }
609
- }
610
-
611
- return true;
612
- }
613
-
614
- private async runPublicationViaWorker(sub: ActiveSubscriptionModel, userId?: string): Promise<PublicationExecution | null> {
615
- if (!this.shouldUsePublicationWorkers()) {
616
- return null;
617
- }
618
-
619
- const dispatcher = this.getPublicationWorkerDispatcher();
620
- if (!dispatcher) {
621
- return null;
622
- }
623
-
624
- try {
625
- const response = await dispatcher.sendInternalPromiseRaw('runPublication', [
626
- sub.publication,
627
- sub.subscriptionData,
628
- userId
629
- ]);
630
-
631
- if (!response || response.error) {
632
- throw response?.result || new Error('Publication worker error');
633
- }
634
-
635
- if (!response.packedResult) {
636
- throw new Error('Publication worker missing packedResult');
637
- }
638
-
639
- const meta = response.meta || {};
640
- const snapshot = meta.snapshot ? deserializeDependencySnapshot(meta.snapshot) : undefined;
641
-
642
- return {
643
- packedResult: response.packedResult,
644
- snapshot,
645
- encoding: response.encoding || meta.encoding || 'msgpack',
646
- workerUsed: true
647
- };
648
- }
649
- catch (error) {
650
- if (this._enableDebug) {
651
- console.log(new Date(), 'Sub Manager', 'Worker publication failed, falling back', sub.publication, error?.message || error);
652
- }
653
- return null;
654
- }
655
- }
656
-
657
- private async runPublicationLocally(sub: ActiveSubscriptionModel, userId?: string): Promise<PublicationExecution> {
658
- const pub = this._publications[sub.publication];
659
- if (!pub) {
660
- throw new Error(`Publication not found: ${sub.publication}`);
661
- }
662
-
663
- const context = Object.assign({}, this, SubscriptionManager.prototype);
664
- const metadata: PublicationContext = {
665
- publication: sub.publication,
666
- subscriptionData: sub.subscriptionData
667
- };
668
-
669
- const execution = pub.user_specific
670
- ? await withDependencyTracking(() => pub.function.call(context, userId || '', ...sub.subscriptionData), Object.assign({}, metadata, { userId }))
671
- : await withDependencyTracking(() => pub.function.call(context, ...sub.subscriptionData), metadata);
672
-
673
- return {
674
- result: execution.result,
675
- packedResult: this.packCachePayload(execution.result),
676
- snapshot: execution.snapshot,
677
- encoding: 'msgpack',
678
- workerUsed: false
679
- };
680
- }
681
-
682
- private async runPublicationExecution(sub: ActiveSubscriptionModel, userId?: string): Promise<PublicationExecution> {
683
- const pub = this._publications[sub.publication];
684
- if (!pub) {
685
- throw new Error(`Publication not found: ${sub.publication}`);
686
- }
687
-
688
- const effectiveUserId = pub.user_specific ? userId : undefined;
689
- const workerExecution = await this.runPublicationViaWorker(sub, effectiveUserId);
690
- if (workerExecution) {
691
- return workerExecution;
692
- }
693
-
694
- return this.runPublicationLocally(sub, effectiveUserId);
695
- }
696
-
697
- private async sendDataToAllWithRetry(sub: ActiveSubscriptionModel, collection: string, type: string) {
698
- sub.running = true;
699
-
700
- do {
701
- if (this._enableDebug) {
702
- console.log(new Date(), 'Running sendDataToAll Sub', sub.publication);
703
- }
704
-
705
- sub.runAgain = false;
706
- await this.sendDataToAll(sub, collection, type);
707
-
708
- if (this._enableDebug) {
709
- console.log(new Date(), 'Done sendDataToAll Sub', sub.publication, sub.runAgain);
710
- }
711
-
712
- if (sub.runAgain) {
713
- await this.delay(500); // delay, adjust as needed
714
- }
715
- } while (sub.runAgain);
716
-
717
- sub.running = false;
718
- }
719
-
720
- private async sendDataToOneWithRetry(ws: WebSocket, messageId: number, sub: ActiveSubscriptionModel, collection: string, type: string) {
721
- sub.running = true;
722
-
723
- do {
724
- if (this._enableDebug) {
725
- console.log(new Date(), 'Running sendDataToOne Sub', sub.publication);
726
- }
727
-
728
- sub.runAgain = false;
729
- await this.sendDataToOne(ws, messageId, sub, collection, type);
730
-
731
- if (this._enableDebug) {
732
- console.log(new Date(), 'Done sendDataToOne Sub', sub.publication, sub.runAgain);
733
- }
734
-
735
- if (sub.runAgain) {
736
- await this.delay(500); // delay, adjust as needed
737
- }
738
- } while (sub.runAgain);
739
- sub.running = false;
740
- }
741
-
742
- // Add all files to publications private object
743
- public publications(method: SubscriptionModel) {
744
- this._publications = Object.assign(this._publications, method);
745
- }
746
-
747
- public getPublication(publication: string): SubscriptionPubModel | undefined {
748
- return this._publications[publication];
749
- }
750
-
751
- public getPublications(): SubscriptionModel {
752
- return this._publications;
753
- }
754
-
755
- // Throttled `loggedInLatency` method
756
- public loggedInLatency(ws: WebSocket) {
757
- const now = new Date();
758
- const newLatency = ws['latency'];
759
- const socketId = ws['id_socket'];
760
- let loggedInUser = this._loggedInUsers.find(a => a.id_ws === socketId);
761
-
762
- if (!loggedInUser) {
763
- return;
764
- }
765
-
766
- const existingEntry = this.latencyBuffer.get(socketId);
767
-
768
- // Throttle updates: only update if time threshold has passed or latency has significantly changed
769
- if (
770
- !existingEntry ||
771
- (now.getTime() - existingEntry.lastUpdate.getTime() >= this.LATENCY_UPDATE_THRESHOLD_MS) ||
772
- (Math.abs(newLatency - existingEntry.latency) > 100) // Optional: log only significant changes
773
- ) {
774
- // Update in-memory and buffer
775
- loggedInUser.date = now;
776
- this.latencyBuffer.set(socketId, { latency: newLatency, lastUpdate: now });
777
- }
778
- }
779
-
780
- // Method to flush buffered latency updates in bulk
781
- private async flushThrottledLatencyUpdates() {
782
- if (this.latencyBuffer.size === 0) return; // No updates to flush
783
- if (this._latencyFlushInProgress) {
784
- this._latencyFlushPending = true;
785
- return;
786
- }
787
-
788
- this._latencyFlushInProgress = true;
789
-
790
- const pendingEntries = Array.from(this.latencyBuffer.entries());
791
- this.latencyBuffer.clear();
792
- const updates = pendingEntries.map(([id_ws, { latency, lastUpdate }]) => ({
793
- updateOne: {
794
- filter: { id_ws },
795
- update: { $set: { latency, date: lastUpdate } }
796
- }
797
- }));
798
-
799
- try {
800
- await LoggedInUsers.bulkWrite(updates, { ordered: false });
801
-
802
- if (this.getEnableDebug()) {
803
- console.log(new Date(), 'Sub Manager', 'Throttled latency batch update successful', updates.length);
804
- }
805
- }
806
- catch (error) {
807
- for (const [id_ws, payload] of pendingEntries) {
808
- const current = this.latencyBuffer.get(id_ws);
809
- if (!current || current.lastUpdate < payload.lastUpdate) {
810
- this.latencyBuffer.set(id_ws, payload);
811
- }
812
- }
813
- console.error(new Date(), 'Sub Manager', 'Throttled latency batch update failed', error);
814
- // Optional: implement retry logic or logging for failed updates
815
- }
816
- finally {
817
- this._latencyFlushInProgress = false;
818
- if (this._latencyFlushPending) {
819
- this._latencyFlushPending = false;
820
- setImmediate(() => this.flushThrottledLatencyUpdates());
821
- }
822
- }
823
- }
824
-
825
- // Subscribe to publication
826
- public async subscribe(messageRoute: string, messageDate: Date, ws: WebSocket, messageId: number, publication: string, subscriptionData: any[]) {
827
- this._debugSubHits += 1;
828
- this.connectDebug('Subscribe request', {
829
- publication,
830
- messageRoute,
831
- messageId,
832
- id_socket: ws ? ws['id_socket'] : null,
833
- user: ws ? ws['user'] : null,
834
- args: Array.isArray(subscriptionData) ? subscriptionData.length : 0
835
- });
836
-
837
- if (!this._debugSubCollections.some(a => a.publication === publication)) {
838
- this._debugSubCollections.push({
839
- publication: publication,
840
- hits: 1
841
- });
842
- }
843
- else {
844
- this._debugSubCollections.find(a => a.publication === publication).hits += 1;
845
- }
846
-
847
- let pub = this._publications[publication];
848
-
849
- if (!pub) {
850
- console.error(new Date(), 'No Publication: ' + publication);
851
- this.connectDebug('Missing publication', { publication, messageRoute, messageId });
852
- return;
853
- }
854
- else {
855
- if (subscriptionData.length > 1 || subscriptionData[0]) {
856
- if (!pub.check) {
857
- console.error(new Date(), 'No Check Function For Pub ' + publication);
858
- this.connectDebug('Missing check for publication', { publication, messageRoute, messageId });
859
- return;
860
- }
861
- else if (!pub.check._schema) {
862
- console.error(new Date(), 'No Check Schema For Pub ' + publication);
863
- this.connectDebug('Missing check schema for publication', { publication, messageRoute, messageId });
864
- return;
865
- }
866
- else {
867
- let valObj = {};
868
- let valKeys = Object.keys(pub.check._schema);
869
-
870
- let rootKeys = valKeys.filter(a => !a.includes('.'));
871
-
872
- for (let i = 0; i < subscriptionData.length; i++) {
873
- valObj[rootKeys[i]] = subscriptionData[i];
874
- }
875
-
876
- try {
877
- pub.check.validate(valObj);
878
- }
879
- catch (errors) {
880
- if (errors) {
881
- console.error(new Date(), 'Error in Pub Check (' + publication + ')', errors);
882
- this.connectDebug('Publication check failed', {
883
- publication,
884
- messageRoute,
885
- messageId,
886
- args: subscriptionData.length,
887
- valObj,
888
- error: errors?.message || errors
889
- });
890
- return;
891
- }
892
- }
893
- }
894
- }
895
-
896
- const staleRouteMessage = this.isStaleRouteMessage(ws, messageRoute, messageDate);
897
- if (messageRoute !== 'Bypass' && messageRoute !== '/' && !staleRouteMessage) {
898
- let urlData = messageRoute.split('/');
899
- let urlModule = '';
900
- let urlNext = urlData[0];
901
-
902
- if (urlData[0] === '') {
903
- urlModule = '/';
904
- urlNext = urlData[1];
905
- }
906
-
907
- urlModule += urlNext;
908
-
909
- if (urlData.length > 1) {
910
- urlModule += '/';
911
- }
912
-
913
- let otherRouteSubs = this._subscriptions.filter(a => a.clients.some(b => b.id_socket === ws['id_socket'] && b.messageRoute !== 'Bypass' && b.messageRoute !== '/' && b.messageRoute !== messageRoute && !b.messageRoute.startsWith(urlModule)));
914
-
915
- if (otherRouteSubs.length) {
916
- // ResolveIOServer.getMainServer().getMethodManager().sendEmail('dev@resolveio.com', 'SERVER - Detected Undestroyed Subscription - ' + this.serverConfig['CLIENT_NAME'], 'USER: ' + ws['user'] + ' (Socket: ' + ws['id_socket'] + ') ' + ' is on route: ' + messageRoute + ' but has the following subscriptions on other routes:' + JSON.stringify(otherRouteSubs, null, 2));
917
-
918
- otherRouteSubs.forEach(otherSub => {
919
- otherSub.clients.filter(a => a.id_socket === ws['id_socket']).forEach(client => {
920
- this.unsubscribe(client.messageRoute, new Date(), ws, client.messageId, otherSub.publication, otherSub.subscriptionData);
921
- });
922
- });
923
- }
924
- }
925
-
926
- const normalizedSubscriptionData = Array.isArray(subscriptionData) ? subscriptionData : [];
927
- const subscriptionKey = JSON.stringify(normalizedSubscriptionData);
928
- let sub = this._subscriptions.find(a => a.publication === publication && a.subscriptionKey === subscriptionKey);
929
- const isAiPublication = this.isAiPublication(publication);
930
- const wsId = ws ? ws['id_socket'] : null;
931
- const wsUserId = ws ? ws['id_user'] : null;
932
-
933
- // If sub found (another user watching same data), add client to same sub
934
- if (sub) {
935
- if (isAiPublication && wsId) {
936
- const existingIndex = sub.clients.findIndex(a => a.id_socket === wsId);
937
- if (existingIndex >= 0) {
938
- const existingClient = sub.clients[existingIndex];
939
- for (let i = sub.clients.length - 1; i >= 0; i--) {
940
- if (i !== existingIndex && sub.clients[i].id_socket === wsId) {
941
- sub.clients.splice(i, 1);
942
- }
943
- }
944
- existingClient.id_user = wsUserId;
945
- existingClient.messageId = messageId;
946
- existingClient.messageRoute = messageRoute;
947
- }
948
- else {
949
- sub.clients.push({
950
- id_user: wsUserId,
951
- messageId: messageId,
952
- id_socket: wsId,
953
- messageRoute: messageRoute
954
- });
955
- }
956
- }
957
- else if (!sub.clients.some(a => a.id_socket === ws['id_socket'] && a.messageId === messageId)) {
958
- sub.clients.push({
959
- id_user: ws['id_user'],
960
- messageId: messageId,
961
- id_socket: ws['id_socket'],
962
- messageRoute: messageRoute
963
- });
964
- }
965
- }
966
- // If sub not found, create new sub
967
- else {
968
- this._subscriptions.push({
969
- publication: publication,
970
- subscriptionKey: subscriptionKey,
971
- subscriptionData: normalizedSubscriptionData,
972
- collections: this.getPublicationCollections(publication),
973
- clients: [{
974
- id_user: ws['id_user'],
975
- messageId: messageId,
976
- id_socket: ws['id_socket'],
977
- messageRoute: messageRoute,
978
- }],
979
- cacheId: 0,
980
- running: false,
981
- runAgain: false,
982
- collectionDependencies: new Map(),
983
- collectionFilters: new Map(),
984
- watchAllCollections: new Set(),
985
- collectionQueryMeta: new Map()
986
- });
987
- }
988
-
989
- if (!sub) {
990
- sub = this._subscriptions.find(a => a.publication === publication && a.subscriptionKey === subscriptionKey);
991
- }
992
- this.ensureDependencyContainers(sub);
993
- this.logAiSubscriptionEvent('sub', {
994
- publication,
995
- subscriptionKey,
996
- messageId,
997
- id_socket: ws ? ws['id_socket'] : null,
998
- messageRoute,
999
- clients: sub?.clients?.length || 0,
1000
- subscriptionData: normalizedSubscriptionData
1001
- });
1002
-
1003
- if (this._enableDebug) {
1004
- console.log(new Date(), 'New Sub', sub.publication, sub.running, sub.runAgain, sub.clients.length);
1005
- }
1006
-
1007
- // Immediately send data to the new client
1008
- await this.processSubscription(sub, ws, messageId);
1009
- }
1010
- }
1011
-
1012
- public async createLoggedInUser(id_ws: string): Promise<LoggedInUserModel> {
1013
- let ws = this._websocketManager.getWebSocket(id_ws);
1014
-
1015
- if (ws) {
1016
- let user = {
1017
- _id: objectIdHexString(),
1018
- __v: 0,
1019
- date: new Date(),
1020
- id_user: ws['id_user'],
1021
- user: ws['user'],
1022
- id_ws: ws['id_socket']
1023
- };
1024
-
1025
- this._loggedInUsers.push(user);
1026
- await LoggedInUsers.insertOne(user);
1027
-
1028
- return user;
1029
- }
1030
- else {
1031
- throw new Error('Error in Create Logged In User: No WS');
1032
- }
1033
- }
1034
-
1035
- // Unsubscribe from publication
1036
- public unsubscribe(messageRoute: string, messageDate: Date, ws: WebSocket, messageId: number, publication: string, subscriptionData: any[]) {
1037
- this._debugUnSubHits += 1;
1038
-
1039
- if (!this._publications[publication]) {
1040
- console.log('No Publication: ' + publication);
1041
- return;
1042
- }
1043
- else {
1044
- const normalizedSubscriptionData = Array.isArray(subscriptionData) ? subscriptionData : [];
1045
- const subscriptionKey = JSON.stringify(normalizedSubscriptionData);
1046
- let sub = this._subscriptions.find(a => a.publication === publication && a.subscriptionKey === subscriptionKey);
1047
-
1048
- if (sub) {
1049
- const isAiPublication = this.isAiPublication(publication);
1050
- for (let i = sub.clients.length - 1; i >= 0; i--) {
1051
- if (isAiPublication && sub.clients[i].id_socket === ws['id_socket']) {
1052
- sub.clients.splice(i, 1);
1053
- }
1054
- else if (sub.clients[i].id_user === ws['id_user'] && sub.clients[i].messageId === messageId && sub.clients[i].id_socket === ws['id_socket']) {
1055
- sub.clients.splice(i, 1);
1056
- }
1057
- }
1058
-
1059
- this.logAiSubscriptionEvent('unsub', {
1060
- publication,
1061
- subscriptionKey,
1062
- messageId,
1063
- id_socket: ws ? ws['id_socket'] : null,
1064
- messageRoute,
1065
- clients: sub.clients.length,
1066
- subscriptionData: normalizedSubscriptionData
1067
- });
1068
-
1069
- if (this._enableDebug) {
1070
- console.log(new Date(), 'Unsub', sub.publication, sub.running, sub.runAgain, sub.clients.length);
1071
- }
1072
- }
1073
- }
1074
- }
1075
-
1076
-
1077
- // Unsubscribe from publication
1078
- public async unsubscribeAll(ws: WebSocket) {
1079
- this._debugUnSubAllHits += 1;
1080
-
1081
- if (ws) {
1082
- if (ws['id_socket']) {
1083
- this._lastRouteBySocket.delete(ws['id_socket']);
1084
- }
1085
-
1086
- // Check if WebSocket has already been unsubscribed
1087
- if (ws['isUnsubscribed']) {
1088
- return; // Skip if already unsubscribed
1089
- }
1090
-
1091
- // Mark the WebSocket as unsubscribed to prevent further calls
1092
- ws['isUnsubscribed'] = true;
1093
-
1094
- if (this._loggedInUsers.map(a => a.id_ws).indexOf(ws['id_socket']) >= 0) {
1095
- this._loggedInUsers.splice(this._loggedInUsers.map(a => a.id_ws).indexOf(ws['id_socket']), 1);
1096
- }
1097
-
1098
- await LoggedInUsers.deleteOne({ id_ws: ws['id_socket'] });
1099
-
1100
- let userSubs = this._subscriptions.filter(a => a.clients.some(b => b.id_user === ws['id_user'] && b.id_socket === ws['id_socket']));
1101
-
1102
- for (let i = userSubs.length - 1; i >= 0; i--) {
1103
- let sub = userSubs[i];
1104
-
1105
- for (let j = sub.clients.length - 1; j >= 0; j--) {
1106
- if (sub.clients[j].id_socket === ws['id_socket']) {
1107
- sub.clients.splice(j, 1);
1108
- }
1109
- }
1110
- }
1111
-
1112
- this._websocketManager.removeWebSocket(ws);
1113
- }
1114
- }
1115
-
1116
- public getActiveSubscriptions() {
1117
- return this._subscriptions;
1118
- }
1119
-
1120
- // Get publication collection
1121
- private getPublicationCollections(publication: string) {
1122
- return this._publications[publication].collections;
1123
- }
1124
-
1125
- private getWatchedDatabases(): string[] {
1126
- const mongoManager = ResolveIOServer.getMongoManager();
1127
- const managerDatabases = mongoManager?.getWatchedDatabases() || [];
1128
-
1129
- if (managerDatabases.length) {
1130
- return managerDatabases;
1131
- }
1132
-
1133
- const config = this.serverConfig || ResolveIOServer.getServerConfig();
1134
- const mainDb = config && typeof config['DATABASE'] === 'string' ? config['DATABASE'] : '';
1135
-
1136
- return mainDb ? [mainDb] : [];
1137
- }
1138
-
1139
- private resolveOplogMode(): 'auto' | 'change-stream' | 'local' {
1140
- const config = this.serverConfig || ResolveIOServer.getServerConfig() || {};
1141
- const raw = process.env.SUBSCRIPTION_OPLOG_MODE || process.env.OPLOG_MODE || config['SUBSCRIPTION_OPLOG_MODE'] || config['OPLOG_MODE'];
1142
-
1143
- if (typeof raw !== 'string' || !raw.trim()) {
1144
- return 'auto';
1145
- }
1146
-
1147
- const normalized = raw.trim().toLowerCase();
1148
-
1149
- if (['local', 'single', 'standalone', 'fallback', 'poll'].includes(normalized)) {
1150
- return 'local';
1151
- }
1152
-
1153
- if (['change-stream', 'changestream', 'oplog', 'replica', 'replicaset', 'rs'].includes(normalized)) {
1154
- return 'change-stream';
1155
- }
1156
-
1157
- return 'auto';
1158
- }
1159
-
1160
- private resolveLocalOplogResyncIntervalMs(): number {
1161
- const config = this.serverConfig || ResolveIOServer.getServerConfig() || {};
1162
- const raw = process.env.OPLOG_FALLBACK_RESYNC_MS
1163
- || process.env.SUBSCRIPTION_OPLOG_RESYNC_MS
1164
- || config['OPLOG_FALLBACK_RESYNC_MS']
1165
- || config['SUBSCRIPTION_OPLOG_RESYNC_MS'];
1166
-
1167
- return this.parsePositiveNumber(raw);
1168
- }
1169
-
1170
- private resolveResumeTokenMaxAgeMs(): number {
1171
- const config = this.serverConfig || ResolveIOServer.getServerConfig() || {};
1172
- const raw = process.env.OPLOG_RESUME_TOKEN_MAX_AGE_MS
1173
- || process.env.SUBSCRIPTION_OPLOG_RESUME_TOKEN_MAX_AGE_MS
1174
- || config['OPLOG_RESUME_TOKEN_MAX_AGE_MS']
1175
- || config['SUBSCRIPTION_OPLOG_RESUME_TOKEN_MAX_AGE_MS'];
1176
-
1177
- return this.parsePositiveNumber(raw);
1178
- }
1179
-
1180
- private resolveConnectDebug(): boolean {
1181
- const config = this.serverConfig || ResolveIOServer.getServerConfig() || {};
1182
- const raw = process.env.WS_CONNECT_DEBUG
1183
- || process.env.CONNECT_DEBUG
1184
- || config['WS_CONNECT_DEBUG']
1185
- || config['CONNECT_DEBUG'];
1186
-
1187
- return this.parseDebugFlag(raw);
1188
- }
1189
-
1190
- private resolveSubSendDebug(): boolean {
1191
- const config = this.serverConfig || ResolveIOServer.getServerConfig() || {};
1192
- const raw = process.env.SUB_SEND_DEBUG
1193
- || process.env.SUBSCRIPTION_SEND_DEBUG
1194
- || config['SUB_SEND_DEBUG']
1195
- || config['SUBSCRIPTION_SEND_DEBUG'];
1196
-
1197
- return this.parseDebugFlag(raw);
1198
- }
1199
-
1200
- private resolveSubSendLogEnabled(): boolean {
1201
- const config = this.serverConfig || ResolveIOServer.getServerConfig() || {};
1202
- const raw = process.env.SUB_SEND_LOG_ENABLED
1203
- || process.env.SUBSCRIPTION_SEND_LOG_ENABLED
1204
- || config['SUB_SEND_LOG_ENABLED']
1205
- || config['SUBSCRIPTION_SEND_LOG_ENABLED'];
1206
-
1207
- if (raw === undefined || raw === null || raw === '') {
1208
- return this._subSendDebug;
1209
- }
1210
-
1211
- return this.parseDebugFlag(raw);
1212
- }
1213
-
1214
- private resolveSubSendLogThresholdMs(): number {
1215
- const config = this.serverConfig || ResolveIOServer.getServerConfig() || {};
1216
- const raw = process.env.SUB_SEND_LOG_THRESHOLD_MS
1217
- || process.env.SUBSCRIPTION_SEND_LOG_THRESHOLD_MS
1218
- || config['SUB_SEND_LOG_THRESHOLD_MS']
1219
- || config['SUBSCRIPTION_SEND_LOG_THRESHOLD_MS'];
1220
-
1221
- return this.parsePositiveNumber(raw);
1222
- }
1223
-
1224
- private resolveSubSendLogBytes(): number {
1225
- const config = this.serverConfig || ResolveIOServer.getServerConfig() || {};
1226
- const raw = process.env.SUB_SEND_LOG_BYTES
1227
- || process.env.SUBSCRIPTION_SEND_LOG_BYTES
1228
- || config['SUB_SEND_LOG_BYTES']
1229
- || config['SUBSCRIPTION_SEND_LOG_BYTES'];
1230
-
1231
- return this.parsePositiveNumber(raw);
1232
- }
1233
-
1234
- private resolveSubSendLogSampleRate(): number {
1235
- const config = this.serverConfig || ResolveIOServer.getServerConfig() || {};
1236
- const raw = process.env.SUB_SEND_LOG_SAMPLE_RATE
1237
- || process.env.SUBSCRIPTION_SEND_LOG_SAMPLE_RATE
1238
- || config['SUB_SEND_LOG_SAMPLE_RATE']
1239
- || config['SUBSCRIPTION_SEND_LOG_SAMPLE_RATE'];
1240
-
1241
- return this.parseSampleRate(raw, this._subSendLogSampleRate);
1242
- }
1243
-
1244
- private resolveAiSubscriptionDebug(): boolean {
1245
- const config = this.serverConfig || ResolveIOServer.getServerConfig() || {};
1246
- const raw = process.env.AI_ASSISTANT_SUB_DEBUG
1247
- || process.env.AI_TERMINAL_SUB_DEBUG
1248
- || config['AI_ASSISTANT_SUB_DEBUG']
1249
- || config['AI_TERMINAL_SUB_DEBUG'];
1250
-
1251
- return this.parseDebugFlag(raw);
1252
- }
1253
-
1254
-
1255
- private parseDebugFlag(value: any): boolean {
1256
- if (value === true) {
1257
- return true;
1258
- }
1259
-
1260
- if (value === false || value === null || value === undefined) {
1261
- return false;
1262
- }
1263
-
1264
- if (typeof value === 'number') {
1265
- return value === 1;
1266
- }
1267
-
1268
- if (typeof value === 'string') {
1269
- const normalized = value.trim().toLowerCase();
1270
- return ['1', 'true', 'yes', 'y', 'on'].includes(normalized);
1271
- }
1272
-
1273
- return false;
1274
- }
1275
-
1276
- private parsePositiveNumber(value: any): number {
1277
- if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
1278
- return value;
1279
- }
1280
-
1281
- if (typeof value === 'string' && value.trim().length) {
1282
- const parsed = parseInt(value, 10);
1283
- if (Number.isFinite(parsed) && parsed > 0) {
1284
- return parsed;
1285
- }
1286
- }
1287
-
1288
- return 0;
1289
- }
1290
-
1291
- private parseSampleRate(value: any, fallback: number): number {
1292
- const parsed = parseFloat(value ?? '');
1293
- if (Number.isFinite(parsed) && parsed > 0 && parsed <= 1) {
1294
- return parsed;
1295
- }
1296
- return fallback;
1297
- }
1298
-
1299
- private shouldLogSubSend(durationMs: number, payloadBytes: number | null): boolean {
1300
- if (!this._subSendLogEnabled) {
1301
- return false;
1302
- }
1303
-
1304
- if (this._subSendLogSampleRate < 1 && Math.random() > this._subSendLogSampleRate) {
1305
- return false;
1306
- }
1307
-
1308
- if (this._subSendDebug) {
1309
- return true;
1310
- }
1311
-
1312
- if (this._subSendLogThresholdMs > 0 && durationMs >= this._subSendLogThresholdMs) {
1313
- return true;
1314
- }
1315
-
1316
- if (payloadBytes !== null && this._subSendLogBytes > 0 && payloadBytes >= this._subSendLogBytes) {
1317
- return true;
1318
- }
1319
-
1320
- return false;
1321
- }
1322
-
1323
- private logSubSend(sub: ActiveSubscriptionModel, details: { collection: string; type: string; clients: number; durationMs: number; payloadBytes: number | null }) {
1324
- if (!details.clients) {
1325
- return;
1326
- }
1327
-
1328
- if (!this.shouldLogSubSend(details.durationMs, details.payloadBytes)) {
1329
- return;
1330
- }
1331
-
1332
- console.log(new Date(), '[Sub Send]', JSON.stringify({
1333
- publication: sub.publication,
1334
- subscriptionKey: sub.subscriptionKey,
1335
- clients: details.clients,
1336
- durationMs: details.durationMs,
1337
- payloadBytes: details.payloadBytes,
1338
- collection: details.collection,
1339
- type: details.type
1340
- }));
1341
- }
1342
-
1343
- private isAiPublication(publication: string): boolean {
1344
- return publication === 'aiTerminalMessages' || publication === 'aiTerminalConversations';
1345
- }
1346
-
1347
- private logAiSubSend(details: {
1348
- publication: string;
1349
- subscriptionKey: string;
1350
- messageId: number;
1351
- id_socket: string;
1352
- clients: number;
1353
- payloadBytes: number | null;
1354
- packed: boolean;
1355
- cacheHit?: boolean;
1356
- collection: string;
1357
- type: string;
1358
- subscriptionData?: any[];
1359
- }): void {
1360
- if (!this._aiWorkerDebug) {
1361
- return;
1362
- }
1363
- if (!this.isAiPublication(details.publication)) {
1364
- return;
1365
- }
1366
- console.log(new Date(), '[AI Worker Debug] subSend', JSON.stringify({
1367
- publication: details.publication,
1368
- subscriptionKey: details.subscriptionKey,
1369
- messageId: details.messageId,
1370
- id_socket: details.id_socket,
1371
- clients: details.clients,
1372
- payloadBytes: details.payloadBytes,
1373
- packed: details.packed,
1374
- cacheHit: details.cacheHit ?? false,
1375
- collection: details.collection,
1376
- type: details.type,
1377
- subscriptionData: details.subscriptionData
1378
- }));
1379
- }
1380
-
1381
- private logAiSubscriptionEvent(event: 'sub' | 'unsub', details: {
1382
- publication: string;
1383
- subscriptionKey: string;
1384
- messageId: number;
1385
- id_socket: string;
1386
- messageRoute: string;
1387
- clients: number;
1388
- subscriptionData?: any[];
1389
- }): void {
1390
- if (!this._aiSubscriptionDebug) {
1391
- return;
1392
- }
1393
- if (!this.isAiPublication(details.publication)) {
1394
- return;
1395
- }
1396
- const wsId = details.id_socket || 'unknown';
1397
- const key = `${event}:${wsId}:${details.publication}:${details.subscriptionKey}`;
1398
- const now = Date.now();
1399
- const lastAt = this._aiSubLastAt.get(key);
1400
- const deltaMs = lastAt ? now - lastAt : null;
1401
- this._aiSubLastAt.set(key, now);
1402
- console.log(new Date(), `[AI Sub Debug] ${event}`, JSON.stringify({
1403
- publication: details.publication,
1404
- subscriptionKey: details.subscriptionKey,
1405
- messageId: details.messageId,
1406
- messageRoute: details.messageRoute,
1407
- id_socket: wsId,
1408
- clients: details.clients,
1409
- deltaMs,
1410
- subscriptionData: details.subscriptionData
1411
- }));
1412
- }
1413
-
1414
- private connectDebug(message: string, details?: Record<string, unknown>) {
1415
- if (!this._connectDebug) {
1416
- return;
1417
- }
1418
-
1419
- if (details) {
1420
- console.log(new Date(), '[Connect Debug]', message, JSON.stringify(details));
1421
- }
1422
- else {
1423
- console.log(new Date(), '[Connect Debug]', message);
1424
- }
1425
- }
1426
-
1427
- private parsePositiveFloat(value: any): number {
1428
- if (typeof value === 'number' && Number.isFinite(value) && value > 0) {
1429
- return value;
1430
- }
1431
-
1432
- if (typeof value === 'string' && value.trim().length) {
1433
- const parsed = parseFloat(value);
1434
- if (Number.isFinite(parsed) && parsed > 0) {
1435
- return parsed;
1436
- }
1437
- }
1438
-
1439
- return 0;
1440
- }
1441
-
1442
- private isChangeStreamUnsupported(error: any): boolean {
1443
- const code = typeof error?.code === 'number' ? error.code : null;
1444
- const codeName = typeof error?.codeName === 'string' ? error.codeName.toLowerCase() : '';
1445
- const message = typeof error?.message === 'string' ? error.message.toLowerCase() : '';
1446
-
1447
- if (code === 40573) {
1448
- return true;
1449
- }
1450
-
1451
- if (codeName && codeName.includes('changestream')) {
1452
- return true;
1453
- }
1454
-
1455
- if (message.includes('change stream') && message.includes('replica')) {
1456
- return true;
1457
- }
1458
-
1459
- if (message.includes('change stream') && message.includes('not supported')) {
1460
- return true;
1461
- }
1462
-
1463
- if (code === 13 && message.includes('allchangesforcluster')) {
1464
- return true;
1465
- }
1466
-
1467
- if (code === 13 && message.includes('not authorized') && message.includes('$db: "admin"')) {
1468
- return true;
1469
- }
1470
-
1471
- return false;
1472
- }
1473
-
1474
- private isResumeTokenInvalid(error: any): boolean {
1475
- const code = typeof error?.code === 'number' ? error.code : null;
1476
- const codeName = typeof error?.codeName === 'string' ? error.codeName.toLowerCase() : '';
1477
- const message = typeof error?.message === 'string' ? error.message.toLowerCase() : '';
1478
-
1479
- if (code === 286) {
1480
- // ChangeStreamHistoryLost
1481
- return true;
1482
- }
1483
-
1484
- if (codeName.includes('changestreamhistorylost')) {
1485
- return true;
1486
- }
1487
-
1488
- if (message.includes('resume token') && (
1489
- message.includes('not found') ||
1490
- message.includes('not possible') ||
1491
- message.includes('no longer') ||
1492
- message.includes('not in the oplog') ||
1493
- message.includes('too old')
1494
- )) {
1495
- return true;
1496
- }
1497
-
1498
- if (message.includes('resume of change stream') && message.includes('not possible')) {
1499
- return true;
1500
- }
1501
-
1502
- return false;
1503
- }
1504
-
1505
- private isChangeStreamDocumentTooLarge(error: any): boolean {
1506
- const code = typeof error?.code === 'number' ? error.code : null;
1507
- const codeName = typeof error?.codeName === 'string' ? error.codeName.toLowerCase() : '';
1508
- const message = typeof error?.message === 'string' ? error.message.toLowerCase() : '';
1509
-
1510
- if (code === 10334) {
1511
- return true;
1512
- }
1513
-
1514
- if (codeName.includes('bsonobjecttoolarge')) {
1515
- return true;
1516
- }
1517
-
1518
- if (message.includes('bsonobj size') && message.includes('16mb')) {
1519
- return true;
1520
- }
1521
-
1522
- return false;
1523
- }
1524
-
1525
- private async enableLocalOplogFallback(reason: string, error?: any) {
1526
- if (this._useLocalOplog) {
1527
- return;
1528
- }
1529
-
1530
- this._useLocalOplog = true;
1531
- this._oplogMode = 'local';
1532
- this._oplogRetryCount = 0;
1533
-
1534
- if (this._oplog$ && !this._oplog$.closed) {
1535
- try {
1536
- this._oplog$.removeAllListeners();
1537
- await this._oplog$.close();
1538
- }
1539
- catch {
1540
- // ignore close errors
1541
- }
1542
- this._oplog$ = null;
1543
- }
1544
-
1545
- const message = typeof error?.message === 'string' ? error.message : '';
1546
- console.log(new Date(), 'oplog fallback enabled', reason, message ? `(${message})` : '');
1547
-
1548
- this.queueFullResync('oplog-fallback-enabled');
1549
-
1550
- if (this._localOplogResyncIntervalMs > 0 && !this._localOplogResyncTimer) {
1551
- this._localOplogResyncTimer = setInterval(() => {
1552
- this.queueFullResync('oplog-fallback-poll');
1553
- }, this._localOplogResyncIntervalMs);
1554
- }
1555
- }
1556
-
1557
- public getUseLocalOplog() {
1558
- return this._useLocalOplog;
1559
- }
1560
-
1561
- public async handleMongoReconnect(reason: string): Promise<void> {
1562
- if (this._useLocalOplog) {
1563
- this.queueFullResync(`mongo-reconnect:${reason}`);
1564
- return;
1565
- }
1566
-
1567
- try {
1568
- const resumeToken = this._lastResumeToken || await this.loadResumeToken();
1569
- await this.tailOpLog(resumeToken || undefined);
1570
- }
1571
- catch (error) {
1572
- console.log(new Date(), 'Sub Manager', 'Mongo reconnect: tailOpLog restart failed', reason, error);
1573
- }
1574
-
1575
- this.queueFullResync(`mongo-reconnect:${reason}`);
1576
- }
1577
-
1578
- public notifyLocalOplog(collection: string, type = 'update') {
1579
- if (!this._useLocalOplog) {
1580
- return;
1581
- }
1582
-
1583
- setImmediate(async () => {
1584
- try {
1585
- await this.invalidatePubsCache(collection, type);
1586
- }
1587
- catch {
1588
- // ignore local oplog cache invalidation errors
1589
- }
1590
- });
1591
- }
1592
-
1593
- private getResumeTokenDocId(): string {
1594
- if (process.env.NODE_APP_INSTANCE) {
1595
- return `oplog:${process.env.NODE_APP_INSTANCE}`;
1596
- }
1597
-
1598
- // Fallback: avoid cross-process contention when NODE_APP_INSTANCE is not set.
1599
- return `oplog:${process.pid}`;
1600
- }
1601
-
1602
- private async loadResumeToken(): Promise<ResumeToken | null> {
1603
- try {
1604
- const db = ResolveIOServer.getMainDB();
1605
- if (!db) {
1606
- return null;
1607
- }
1608
-
1609
- const loadToken = async () => {
1610
- const collection = db.collection<OplogResumeTokenDocument>(this.RESUME_TOKEN_COLLECTION);
1611
- const doc = await collection.findOne({ _id: this.getResumeTokenDocId() });
1612
- const token = doc?.token || null;
1613
-
1614
- if (!token) {
1615
- return null;
1616
- }
1617
-
1618
- const maxAgeMs = this.resolveResumeTokenMaxAgeMs();
1619
- if (maxAgeMs > 0 && doc?.updatedAt instanceof Date) {
1620
- const ageMs = Date.now() - doc.updatedAt.getTime();
1621
- if (ageMs > maxAgeMs) {
1622
- await collection.deleteOne({ _id: this.getResumeTokenDocId() });
1623
- console.log(new Date(), 'Sub Manager', 'Resume token expired, clearing', round(ageMs / 1000), 's');
1624
- return null;
1625
- }
1626
- }
1627
-
1628
- return token;
1629
- };
1630
-
1631
- const mongoManager = ResolveIOServer.getMongoManager();
1632
- return mongoManager ? await mongoManager.runWithoutSession(loadToken) : await loadToken();
1633
- }
1634
- catch (error) {
1635
- console.log(new Date(), 'Sub Manager', 'Failed to load oplog resume token', error);
1636
- return null;
1637
- }
1638
- }
1639
-
1640
- private async saveResumeToken(token: ResumeToken): Promise<void> {
1641
- if (!token) {
1642
- return;
1643
- }
1644
-
1645
- try {
1646
- const db = ResolveIOServer.getMainDB();
1647
- if (!db) {
1648
- return;
1649
- }
1650
-
1651
- const saveToken = async () => {
1652
- await db.collection<OplogResumeTokenDocument>(this.RESUME_TOKEN_COLLECTION).updateOne(
1653
- { _id: this.getResumeTokenDocId() },
1654
- { $set: { token, updatedAt: new Date() } },
1655
- { upsert: true }
1656
- );
1657
- };
1658
-
1659
- const mongoManager = ResolveIOServer.getMongoManager();
1660
- if (mongoManager) {
1661
- await mongoManager.runWithoutSession(saveToken);
1662
- }
1663
- else {
1664
- await saveToken();
1665
- }
1666
- }
1667
- catch (error) {
1668
- console.log(new Date(), 'Sub Manager', 'Failed to persist oplog resume token', error);
1669
- }
1670
- }
1671
-
1672
- private async clearResumeToken(): Promise<void> {
1673
- try {
1674
- const db = ResolveIOServer.getMainDB();
1675
- if (!db) {
1676
- return;
1677
- }
1678
-
1679
- const clearToken = async () => {
1680
- await db.collection<OplogResumeTokenDocument>(this.RESUME_TOKEN_COLLECTION)
1681
- .deleteOne({ _id: this.getResumeTokenDocId() });
1682
- };
1683
-
1684
- const mongoManager = ResolveIOServer.getMongoManager();
1685
- if (mongoManager) {
1686
- await mongoManager.runWithoutSession(clearToken);
1687
- }
1688
- else {
1689
- await clearToken();
1690
- }
1691
- }
1692
- catch (error) {
1693
- console.log(new Date(), 'Sub Manager', 'Failed to clear oplog resume token', error);
1694
- }
1695
- }
1696
-
1697
- private queueResumeTokenSave(token: ResumeToken, options?: { force?: boolean }) {
1698
- if (!token) {
1699
- return;
1700
- }
1701
-
1702
- this._lastResumeToken = token;
1703
-
1704
- const now = Date.now();
1705
- const shouldSave = !!options?.force || (now - this._lastResumeTokenSaveMs) >= this.RESUME_TOKEN_SAVE_INTERVAL_MS;
1706
-
1707
- if (!shouldSave) {
1708
- return;
1709
- }
1710
-
1711
- if (this._resumeTokenSavePromise !== null) {
1712
- if (options?.force) {
1713
- this._resumeTokenSaveForcePending = true;
1714
- }
1715
- return;
1716
- }
1717
-
1718
- this._lastResumeTokenSaveMs = now;
1719
- const tokenToSave = this._lastResumeToken;
1720
-
1721
- this._resumeTokenSavePromise = (async () => {
1722
- try {
1723
- await this.saveResumeToken(tokenToSave);
1724
- }
1725
- finally {
1726
- this._resumeTokenSavePromise = null;
1727
- if (this._resumeTokenSaveForcePending) {
1728
- this._resumeTokenSaveForcePending = false;
1729
- this.queueResumeTokenSave(this._lastResumeToken, { force: true });
1730
- }
1731
- }
1732
- })();
1733
- }
1734
-
1735
- private queueFullResync(reason: string) {
1736
- if (this._fullResyncPromise !== null) {
1737
- return;
1738
- }
1739
-
1740
- this._fullResyncPromise = (async () => {
1741
- try {
1742
- await this.fullResyncSubscriptions(reason);
1743
- }
1744
- finally {
1745
- this._fullResyncPromise = null;
1746
- }
1747
- })();
1748
- }
1749
-
1750
- private async fullResyncSubscriptions(reason: string): Promise<void> {
1751
- try {
1752
- if (this._enableDebug) {
1753
- console.log(new Date(), 'Sub Manager', 'Full subscription resync', reason, this._subscriptions.length);
1754
- }
1755
-
1756
- try {
1757
- ResolveIOServer.getMongoManager()?.clearQueryCache?.(reason);
1758
- }
1759
- catch {
1760
- // ignore cache-clear errors
1761
- }
1762
-
1763
- try {
1764
- this._nodeCache?.flushAll?.();
1765
- }
1766
- catch {
1767
- // ignore cache-clear errors
1768
- }
1769
-
1770
- const subs = this._subscriptions.slice();
1771
-
1772
- for (const sub of subs) {
1773
- if (!sub?.clients?.length) {
1774
- continue;
1775
- }
1776
-
1777
- const pub = this._publications[sub.publication];
1778
- if (!pub) {
1779
- continue;
1780
- }
1781
-
1782
- if (sub.running) {
1783
- sub.runAgain = true;
1784
- continue;
1785
- }
1786
-
1787
- if (pub.user_specific) {
1788
- for (const client of sub.clients) {
1789
- const ws = this._websocketManager.getWebSocket(client.id_socket);
1790
- if (ws?.readyState === WebSocket.OPEN) {
1791
- try {
1792
- await this.sendDataToOneWithRetry(ws, client.messageId, sub, '', reason);
1793
- }
1794
- catch {
1795
- // ignore individual failures
1796
- }
1797
- }
1798
- }
1799
- }
1800
- else {
1801
- await this.sendDataToAllWithRetry(sub, '', reason);
1802
- }
1803
- }
1804
- }
1805
- catch (error) {
1806
- console.log(new Date(), 'Sub Manager', 'Full resync failed', reason, error);
1807
- }
1808
- }
1809
-
1810
- // Watch (tail) Mongo's operation log on the entire database (all insert/modify/delete will trigger this function)
1811
- private async tailOpLog(resumeToken?: ResumeToken) {
1812
- if (this._useLocalOplog) {
1813
- return;
1814
- }
1815
-
1816
- if (this._oplog$ && !this._oplog$.closed) {
1817
- this._oplog$.removeAllListeners();
1818
- await this._oplog$.close();
1819
- this._oplog$ = null;
1820
- }
1821
-
1822
- // eslint-disable-next-line no-restricted-syntax
1823
- await new Promise(resolve => setTimeout(resolve, 1000));
1824
-
1825
- if (!this._oplog$ || this._oplog$.closed) {
1826
- this._oplogRetryCount += 1;
1827
-
1828
- if (this._oplogRetryCount > 5) {
1829
- console.error('****************** TAIL OPLOG ERROR, RETRYING A BUNCH OF TIMES, KILLING PROCESS **************************');
1830
- process.exit(1);
1831
- }
1832
-
1833
- const watchDatabases = this.getWatchedDatabases();
1834
- const pipeline = [
1835
- {
1836
- $match: {
1837
- $and: [
1838
- ...(watchDatabases.length ? [{ 'ns.db': { $in: watchDatabases } }] : []),
1839
- {'ns.coll': { $nin: [
1840
- 'log-method-latencies',
1841
- 'log-subscriptions',
1842
- 'logs',
1843
- 'counters',
1844
- 'cron-job-histories',
1845
- 'email-histories',
1846
- 'qb-soap-request-histories',
1847
- 'qb-soap-request-responses',
1848
- 'qb-soap-requests',
1849
- 'qb-soap-retries',
1850
- this.RESUME_TOKEN_COLLECTION
1851
- ] }},
1852
- {'ns.coll': { $not: /.*\.versions$/ }},
1853
- {'ns.coll': { $not: /^monitor-/ }},
1854
- ]
1855
- },
1856
- },
1857
- ];
1858
-
1859
- let lastResumeToken: ResumeToken;
1860
- let startedWithResumeToken = false;
1861
- let sawChangeEvent = false;
1862
- let resumeTokenInvalidated = false;
1863
- const streamStartedAtMs = Date.now();
1864
-
1865
- if (resumeToken) {
1866
- lastResumeToken = resumeToken;
1867
- try {
1868
- this._oplog$ = ResolveIOServer.getMongoConnection().watch(pipeline, { resumeAfter: resumeToken, fullDocument: 'updateLookup' });
1869
- startedWithResumeToken = true;
1870
- }
1871
- catch (error) {
1872
- if (this.isChangeStreamUnsupported(error)) {
1873
- await this.enableLocalOplogFallback('change-stream-unsupported', error);
1874
- return;
1875
- }
1876
- if (this.isChangeStreamDocumentTooLarge(error)) {
1877
- await this.clearResumeToken();
1878
- lastResumeToken = null;
1879
- this.queueFullResync('oplog-change-stream-document-too-large');
1880
- await this.enableLocalOplogFallback('change-stream-document-too-large', error);
1881
- return;
1882
- }
1883
-
1884
- if (this._oplog$) {
1885
- this._oplog$.removeAllListeners();
1886
- await this._oplog$.close();
1887
- this._oplog$ = null;
1888
- }
1889
-
1890
- await this.clearResumeToken();
1891
- lastResumeToken = null;
1892
-
1893
- console.log(new Date(), 'oplog resumeAfter failed, starting fresh', error);
1894
-
1895
- try {
1896
- this._oplog$ = ResolveIOServer.getMongoConnection().watch(pipeline, { fullDocument: 'updateLookup' });
1897
- startedWithResumeToken = false;
1898
- }
1899
- catch (innerError) {
1900
- if (this.isChangeStreamUnsupported(innerError)) {
1901
- await this.enableLocalOplogFallback('change-stream-unsupported', innerError);
1902
- return;
1903
- }
1904
-
1905
- throw innerError;
1906
- }
1907
-
1908
- this.queueFullResync('oplog-resumeAfter-failed');
1909
- }
1910
- }
1911
- else {
1912
- try {
1913
- this._oplog$ = ResolveIOServer.getMongoConnection().watch(pipeline, { fullDocument: 'updateLookup' });
1914
- }
1915
- catch (error) {
1916
- if (this.isChangeStreamUnsupported(error)) {
1917
- await this.enableLocalOplogFallback('change-stream-unsupported', error);
1918
- return;
1919
- }
1920
- if (this.isChangeStreamDocumentTooLarge(error)) {
1921
- await this.enableLocalOplogFallback('change-stream-document-too-large', error);
1922
- return;
1923
- }
1924
-
1925
- throw error;
1926
- }
1927
- }
1928
-
1929
- console.log(new Date(), 'oplog started', startedWithResumeToken ? '(resumeAfter)' : '');
1930
-
1931
- this._oplog$.on('change', async (doc: ChangeStreamInsertDocument | ChangeStreamUpdateDocument | ChangeStreamReplaceDocument | ChangeStreamDeleteDocument) => {
1932
- sawChangeEvent = true;
1933
- this._oplogRetryCount = 0;
1934
-
1935
- if (doc.ns) {
1936
- if (this._enableDebug) {
1937
- console.log(new Date(), 'Oplog Hit', doc.ns);
1938
- }
1939
-
1940
- let collection = doc.ns.coll;
1941
-
1942
- if (!this._debugOplogCollections.some(a => a.collection === doc.ns.coll && a.type === doc.operationType)) {
1943
- this._debugOplogCollections.push({
1944
- collection: doc.ns.coll,
1945
- type: doc.operationType,
1946
- hits: 1
1947
- });
1948
- }
1949
- else {
1950
- this._debugOplogCollections.find(a => a.collection === doc.ns.coll && a.type === doc.operationType).hits += 1;
1951
- }
1952
-
1953
- if (collection) {
1954
- this._debugOplogHits += 1;
1955
- const fullDocument = doc['fullDocument'];
1956
- const docId = doc.documentKey && doc.documentKey['_id'] !== undefined ? doc.documentKey['_id'] : (fullDocument && fullDocument['_id'] !== undefined ? fullDocument['_id'] : null);
1957
-
1958
- if (doc.operationType === 'insert') {
1959
- if (collection === 'support-tickets') {
1960
- if (this.serverConfig['ROOT_URL'] === 'https://resolveio.com') {
1961
- await ResolveIOServer.getMainServer().getMethodManager().callMethod.call(ResolveIOServer.getMainServer().getMethodManager(), 'sendSupportTicketEmail', doc.documentKey['_id']);
1962
- await this.invalidatePubsCache(collection, 'insert', docId, fullDocument);
1963
- }
1964
- }
1965
-
1966
- if (collection !== 'method-responses') {
1967
- await this.invalidatePubsCache(collection, 'insert', docId, fullDocument);
1968
- }
1969
- }
1970
- else if (doc.operationType === 'update' || doc.operationType === 'replace') {
1971
- if (collection !== 'method-responses') {
1972
- await this.invalidatePubsCache(collection, 'update', docId, fullDocument);
1973
- }
1974
- }
1975
- else if (doc.operationType === 'delete') {
1976
- if (collection !== 'method-responses') {
1977
- await this.invalidatePubsCache(collection, 'delete', docId);
1978
- }
1979
- }
1980
- }
1981
-
1982
- if (collection === 'flags') {
1983
- let flag = await Flags.findOne({ type: 'Enable Debug' });
1984
- let dependencyFlag = await Flags.findOne({ type: 'Enable Dependency Debug' });
1985
-
1986
- if (flag && flag.value) {
1987
- this._enableDebug = true;
1988
- }
1989
- else {
1990
- this._enableDebug = false;
1991
- }
1992
-
1993
- if (dependencyFlag && dependencyFlag.value) {
1994
- this._enableDependencyDebug = true;
1995
- }
1996
- else {
1997
- this._enableDependencyDebug = process.env.ENABLE_DEPENDENCY_DEBUG === 'true';
1998
- }
1999
- }
2000
-
2001
- lastResumeToken = doc._id;
2002
- this.queueResumeTokenSave(lastResumeToken);
2003
-
2004
- if ((!process.env.NODE_APP_INSTANCE || process.env.NODE_APP_INSTANCE === '0') && (process.env.IS_WORKERS_ENABLED === 'false' || (process.env.IS_WORKER_INSTANCE === 'true' && process.env.WORKER_INDEX === '0'))) {
2005
-
2006
- }
2007
- }
2008
- });
2009
-
2010
- this._oplog$.on('error', async error => {
2011
- console.log(new Date(), 'oplog error', error);
2012
- if (this.isChangeStreamUnsupported(error)) {
2013
- await this.enableLocalOplogFallback('change-stream-unsupported', error);
2014
- return;
2015
- }
2016
- if (this.isChangeStreamDocumentTooLarge(error)) {
2017
- await this.clearResumeToken();
2018
- lastResumeToken = null;
2019
- this.queueFullResync('oplog-change-stream-document-too-large');
2020
- await this.enableLocalOplogFallback('change-stream-document-too-large', error);
2021
- return;
2022
- }
2023
-
2024
- if (this.isResumeTokenInvalid(error)) {
2025
- resumeTokenInvalidated = true;
2026
- await this.clearResumeToken();
2027
- lastResumeToken = null;
2028
- this.queueFullResync('oplog-resume-token-invalid');
2029
- }
2030
-
2031
- await this._oplog$.close();
2032
- });
2033
-
2034
- this._oplog$.on('end', async () => {
2035
- console.log(new Date(), 'oplog end');
2036
- await this._oplog$.close();
2037
- });
2038
-
2039
- this._oplog$.on('close', async () => {
2040
- console.log(new Date(), 'oplog close');
2041
- const retryThresholdReached = this._oplogRetryCount >= this.RESUME_TOKEN_AUTO_HEAL_RETRY_THRESHOLD;
2042
- const shouldAutoHealResumeToken = startedWithResumeToken
2043
- && !!lastResumeToken
2044
- && !sawChangeEvent
2045
- && (resumeTokenInvalidated || retryThresholdReached);
2046
-
2047
- if (shouldAutoHealResumeToken) {
2048
- await this.clearResumeToken();
2049
- lastResumeToken = null;
2050
- if (!resumeTokenInvalidated) {
2051
- this.queueFullResync('oplog-resume-token-auto-heal');
2052
- }
2053
- console.log(
2054
- new Date(),
2055
- 'Sub Manager',
2056
- 'Auto-healed stale oplog resume token',
2057
- { retryCount: this._oplogRetryCount, streamLifetimeMs: Date.now() - streamStartedAtMs }
2058
- );
2059
- }
2060
-
2061
- if (lastResumeToken) {
2062
- this.queueResumeTokenSave(lastResumeToken, { force: true });
2063
- }
2064
- this._oplog$.removeAllListeners();
2065
- this._oplog$ = null;
2066
- await this.tailOpLog(lastResumeToken);
2067
- });
2068
- }
2069
- }
2070
-
2071
- private packCachePayload(data: any): Buffer | null {
2072
- if (data === undefined) {
2073
- return null;
2074
- }
2075
-
2076
- try {
2077
- const packed = pack(data);
2078
- return Buffer.isBuffer(packed) ? packed : Buffer.from(packed);
2079
- }
2080
- catch (err) {
2081
- if (this._enableDebug) {
2082
- console.log(new Date(), 'Sub Cache', 'Pack Failed', err?.message || err);
2083
- }
2084
- return null;
2085
- }
2086
- }
2087
-
2088
- private decodeCachePayload(raw: any): any | null {
2089
- if (!raw) {
2090
- return null;
2091
- }
2092
-
2093
- if (Buffer.isBuffer(raw)) {
2094
- try {
2095
- return unpack(raw);
2096
- }
2097
- catch (err) {
2098
- if (this._enableDebug) {
2099
- console.log(new Date(), 'Sub Cache', 'Unpack Failed', err?.message || err);
2100
- }
2101
- return null;
2102
- }
2103
- }
2104
-
2105
- if (raw instanceof Uint8Array) {
2106
- try {
2107
- return unpack(raw);
2108
- }
2109
- catch (err) {
2110
- if (this._enableDebug) {
2111
- console.log(new Date(), 'Sub Cache', 'Unpack Failed', err?.message || err);
2112
- }
2113
- return null;
2114
- }
2115
- }
2116
-
2117
- if (raw instanceof ArrayBuffer) {
2118
- try {
2119
- return unpack(new Uint8Array(raw));
2120
- }
2121
- catch (err) {
2122
- if (this._enableDebug) {
2123
- console.log(new Date(), 'Sub Cache', 'Unpack Failed', err?.message || err);
2124
- }
2125
- return null;
2126
- }
2127
- }
2128
-
2129
- // Treat legacy string caches as invalid to avoid JSON parsing in the hot path.
2130
- if (typeof raw === 'string') {
2131
- return null;
2132
- }
2133
-
2134
- return raw;
2135
- }
2136
-
2137
- private getCacheBuffer(raw: any): Buffer | null {
2138
- if (!raw) {
2139
- return null;
2140
- }
2141
-
2142
- if (Buffer.isBuffer(raw)) {
2143
- return raw;
2144
- }
2145
-
2146
- if (raw instanceof Uint8Array) {
2147
- return Buffer.from(raw);
2148
- }
2149
-
2150
- if (raw instanceof ArrayBuffer) {
2151
- return Buffer.from(new Uint8Array(raw));
2152
- }
2153
-
2154
- return null;
2155
- }
2156
-
2157
- private shouldCachePayload(sub: ActiveSubscriptionModel, payload: Buffer | Uint8Array | null): boolean {
2158
- if (!payload || payload.byteLength >= 1000000) {
2159
- return false;
2160
- }
2161
-
2162
- if (sub.collections.includes('logs')) {
2163
- return false;
2164
- }
2165
-
2166
- if (sub.collections.find(a => a.endsWith('.versions'))) {
2167
- return false;
2168
- }
2169
-
2170
- if (sub.collections.find(a => a.startsWith('monitor-'))) {
2171
- return false;
2172
- }
2173
-
2174
- return true;
2175
- }
2176
-
2177
- private buffersEqual(a: Uint8Array | Buffer | null, b: Uint8Array | Buffer | null): boolean {
2178
- if (!a || !b) {
2179
- return false;
2180
- }
2181
-
2182
- if (a.byteLength !== b.byteLength) {
2183
- return false;
2184
- }
2185
-
2186
- const toBuffer = (value: Uint8Array | Buffer): Buffer => {
2187
- if (Buffer.isBuffer(value)) {
2188
- return value;
2189
- }
2190
- const buffer = value.buffer as ArrayBuffer;
2191
- return Buffer.from(buffer, value.byteOffset, value.byteLength);
2192
- };
2193
-
2194
- const aBuf = toBuffer(a);
2195
- const bBuf = toBuffer(b);
2196
- return aBuf.equals(bBuf as Uint8Array);
2197
- }
2198
-
2199
- private async processSubscription(sub: ActiveSubscriptionModel, ws: WebSocket, messageId: number) {
2200
- if (!this._publications[sub.publication].user_specific) {
2201
- if (sub.cacheId) {
2202
- try {
2203
- const cachedRaw = this._nodeCache.get(sub.cacheId);
2204
- const cachedBuffer = this.getCacheBuffer(cachedRaw);
2205
-
2206
- if (cachedBuffer && cachedBuffer.byteLength) {
2207
- if (this._enableDebug) {
2208
- console.log(new Date(), 'Process Sub, Cache (packed)', sub.publication);
2209
- }
2210
-
2211
- this._websocketManager.sendPackedBuffer(ws, messageId, false, cachedBuffer, 'msgpack');
2212
- this.logAiSubSend({
2213
- publication: sub.publication,
2214
- subscriptionKey: sub.subscriptionKey,
2215
- messageId,
2216
- id_socket: ws['id_socket'],
2217
- clients: 1,
2218
- payloadBytes: cachedBuffer.byteLength,
2219
- packed: true,
2220
- cacheHit: true,
2221
- collection: '',
2222
- type: 'cache',
2223
- subscriptionData: sub.subscriptionData
2224
- });
2225
- }
2226
- else {
2227
- const cacheData = this.decodeCachePayload(cachedRaw);
2228
- if (cacheData === null || cacheData === undefined) {
2229
- throw new Error('cache-miss');
2230
- }
2231
-
2232
- let serverRes: ServerResponseModel = {
2233
- messageId: messageId,
2234
- hasError: false,
2235
- data: cacheData
2236
- };
2237
-
2238
- if (this._enableDebug) {
2239
- console.log(new Date(), 'Process Sub, Cache', sub.publication);
2240
- }
2241
-
2242
- this.sendWS(ws, serverRes);
2243
- this.logAiSubSend({
2244
- publication: sub.publication,
2245
- subscriptionKey: sub.subscriptionKey,
2246
- messageId,
2247
- id_socket: ws['id_socket'],
2248
- clients: 1,
2249
- payloadBytes: cacheData ? Buffer.byteLength(JSON.stringify(cacheData)) : null,
2250
- packed: false,
2251
- cacheHit: true,
2252
- collection: '',
2253
- type: 'cache',
2254
- subscriptionData: sub.subscriptionData
2255
- });
2256
- }
2257
- }
2258
- catch {
2259
- this._nodeCache.del(sub.cacheId);
2260
- sub.cacheId = 0;
2261
-
2262
- await this.sendDataToAllWithRetry(sub, '', 'newSub');
2263
- }
2264
- }
2265
- else {
2266
- if (this._enableDebug) {
2267
- console.log(new Date(), 'Process Sub, Non - Cache', sub.publication, sub.running);
2268
- }
2269
-
2270
- if (sub.running) {
2271
- return;
2272
- }
2273
-
2274
- await this.sendDataToAllWithRetry(sub, '', 'newSub');
2275
- }
2276
- }
2277
- else {
2278
- if (this._enableDebug) {
2279
- console.log(new Date(), 'Process Sub Specific, Non - Cache', sub.publication, sub.running);
2280
- }
2281
-
2282
- if (sub.running) {
2283
- return;
2284
- }
2285
-
2286
- await this.sendDataToOneWithRetry(ws, messageId, sub, '', 'newSub');
2287
- }
2288
- }
2289
-
2290
- private async sendDataToOne(ws: WebSocket, messageId: number, sub: ActiveSubscriptionModel, collection: string, type: string) {
2291
- if (!ws || ws.readyState !== WebSocket.OPEN) {
2292
- return;
2293
- }
2294
-
2295
- let monitor = this._monitorManagerFunction.startMonitorFunction('User Specific Publication', sub.publication, '', '', sub.subscriptionData);
2296
- let dependencySnapshot: DependencyContextSnapshot;
2297
- let res;
2298
- try {
2299
- await ResolveIOServer.getMainServer().getMethodManager().callMethod.call(ResolveIOServer.getMainServer().getMethodManager(), 'insertSubscriptionLog', type, sub.publication, collection, JSON.stringify(sub.subscriptionData));
2300
-
2301
- const execution = await this.runPublicationExecution(sub, ws['id_user']);
2302
- res = execution.result;
2303
- dependencySnapshot = execution.snapshot;
2304
- this.updateSubscriptionDependencies(sub, dependencySnapshot);
2305
- const packedRes = execution.packedResult ?? this.packCachePayload(res);
2306
- const payloadBytes = packedRes ? packedRes.byteLength : null;
2307
-
2308
- if (execution.packedResult) {
2309
- this._websocketManager.sendPackedBuffer(
2310
- ws,
2311
- messageId,
2312
- false,
2313
- execution.packedResult,
2314
- execution.encoding || 'msgpack',
2315
- { passThrough: !!execution.workerUsed }
2316
- );
2317
- }
2318
- else {
2319
- let serverRes: ServerResponseModel = {
2320
- messageId: messageId,
2321
- hasError: false,
2322
- data: res
2323
- };
2324
-
2325
- this.sendWS(ws, serverRes);
2326
- }
2327
- this.logAiSubSend({
2328
- publication: sub.publication,
2329
- subscriptionKey: sub.subscriptionKey,
2330
- messageId,
2331
- id_socket: ws['id_socket'],
2332
- clients: 1,
2333
- payloadBytes,
2334
- packed: !!execution.packedResult,
2335
- cacheHit: false,
2336
- collection,
2337
- type,
2338
- subscriptionData: sub.subscriptionData
2339
- });
2340
- }
2341
- catch (err) {
2342
- const { error: normalizedError, correlationId } = ensureErrorWithCorrelation(err);
2343
-
2344
- let serverRes: ServerResponseModel = {
2345
- messageId: messageId,
2346
- hasError: true,
2347
- data: Object.assign({}, normalizedError, { correlationId })
2348
- };
2349
-
2350
- this.sendWS(ws, serverRes);
2351
-
2352
- const errorPayload = {
2353
- publication: sub.publication,
2354
- subscriptionData: sub.subscriptionData,
2355
- type,
2356
- collection,
2357
- messageId,
2358
- correlationId,
2359
- error: {
2360
- name: normalizedError?.name,
2361
- message: normalizedError?.message,
2362
- stack: normalizedError?.stack,
2363
- code: normalizedError?.code,
2364
- codeName: normalizedError?.codeName
2365
- }
2366
- };
2367
-
2368
- await ErrorReporter.report({
2369
- sourceApp: 'subscription-manager',
2370
- message: 'SERVER - Error Detected - ' + this.serverConfig['CLIENT_NAME'],
2371
- environment: this.serverConfig?.ROOT_URL,
2372
- clientSlug: ResolveIOServer.getClientName(),
2373
- clientName: this.serverConfig['CLIENT_NAME'],
2374
- stack: normalizedError?.stack,
2375
- context: errorPayload,
2376
- metadata: {
2377
- context: 'subscription-sendDataToOne',
2378
- publication: sub.publication,
2379
- correlationId
2380
- },
2381
- correlationId
2382
- });
2383
- }
2384
- finally {
2385
- await this._monitorManagerFunction.finishMonitorFunction(monitor);
2386
- }
2387
- }
2388
-
2389
- // Fetch pub once, send to all clients linked to this pub
2390
- private async sendDataToAll(sub: ActiveSubscriptionModel, collection: string, type: string) {
2391
- const isAiPublication = this.isAiPublication(sub.publication);
2392
- if (isAiPublication && sub.clients.length > 1) {
2393
- const seenSockets = new Set<string>();
2394
- sub.clients = sub.clients.filter(client => {
2395
- if (seenSockets.has(client.id_socket)) {
2396
- return false;
2397
- }
2398
- seenSockets.add(client.id_socket);
2399
- return true;
2400
- });
2401
- }
2402
- const activeClients: { client: ActiveSubscriptionClientModel; ws: WebSocket }[] = [];
2403
- if (sub.clients.length) {
2404
- for (const client of sub.clients) {
2405
- const ws = this._websocketManager.getWebSocket(client.id_socket);
2406
- if (ws && ws.readyState === WebSocket.OPEN) {
2407
- activeClients.push({ client, ws });
2408
- }
2409
- }
2410
- }
2411
-
2412
- if (!activeClients.length) {
2413
- if (sub.cacheId) {
2414
- this._nodeCache.del(sub.cacheId);
2415
- sub.cacheId = 0;
2416
- }
2417
-
2418
- let subIndex = this._subscriptions.findIndex(a => a.publication === sub.publication && a.subscriptionKey === sub.subscriptionKey);
2419
- if (subIndex >= 0) {
2420
- this._subscriptions.splice(subIndex, 1);
2421
- }
2422
-
2423
- return;
2424
- }
2425
- else {
2426
- if (activeClients.length !== sub.clients.length) {
2427
- sub.clients = activeClients.map(entry => entry.client);
2428
- }
2429
-
2430
- let monitor = this._monitorManagerFunction.startMonitorFunction('Publication', sub.publication, '', '', sub.subscriptionData);
2431
- let res;
2432
- let dependencySnapshot: DependencyContextSnapshot;
2433
-
2434
- try {
2435
- if (sub.publication !== 'superadminAPM' && sub.publication !== 'loggedInUsers') {
2436
- await ResolveIOServer.getMainServer().getMethodManager().callMethod.call(ResolveIOServer.getMainServer().getMethodManager(), 'insertSubscriptionLog', type, sub.publication, collection, JSON.stringify(sub.subscriptionData));
2437
- }
2438
-
2439
- const execution = await this.runPublicationExecution(sub);
2440
- res = execution.result;
2441
- dependencySnapshot = execution.snapshot;
2442
- this.updateSubscriptionDependencies(sub, dependencySnapshot);
2443
- const packedRes = execution.packedResult ?? this.packCachePayload(res);
2444
- const passThrough = !!execution.workerUsed;
2445
- const payloadBytes = packedRes ? packedRes.byteLength : null;
2446
- const shouldCache = this.shouldCachePayload(sub, packedRes);
2447
-
2448
-
2449
- if (sub.cacheId) {
2450
- const cachedBuffer = this.getCacheBuffer(this._nodeCache.get(sub.cacheId));
2451
- const isSame = this.buffersEqual(cachedBuffer, packedRes);
2452
-
2453
- if (!isSame) {
2454
- const sendStartMs = Date.now();
2455
- let sentClients = 0;
2456
- for (const entry of activeClients) {
2457
- const { client, ws } = entry;
2458
- if (packedRes) {
2459
- this._websocketManager.sendPackedBuffer(
2460
- ws,
2461
- client.messageId,
2462
- false,
2463
- packedRes,
2464
- execution.encoding || 'msgpack',
2465
- { passThrough }
2466
- );
2467
- }
2468
- else {
2469
- let serverRes: ServerResponseModel = {
2470
- messageId: client.messageId,
2471
- hasError: false,
2472
- data: res
2473
- };
2474
-
2475
- this.sendWS(ws, serverRes);
2476
- }
2477
- if (isAiPublication) {
2478
- this.logAiSubSend({
2479
- publication: sub.publication,
2480
- subscriptionKey: sub.subscriptionKey,
2481
- messageId: client.messageId,
2482
- id_socket: ws['id_socket'],
2483
- clients: activeClients.length,
2484
- payloadBytes,
2485
- packed: !!packedRes,
2486
- cacheHit: false,
2487
- collection,
2488
- type,
2489
- subscriptionData: sub.subscriptionData
2490
- });
2491
- }
2492
- sentClients += 1;
2493
- }
2494
- this.logSubSend(sub, {
2495
- collection,
2496
- type,
2497
- clients: sentClients,
2498
- durationMs: Date.now() - sendStartMs,
2499
- payloadBytes
2500
- });
2501
-
2502
- this._nodeCache.del(sub.cacheId);
2503
-
2504
- if (shouldCache) {
2505
- this._nodeCache.set(sub.cacheId, packedRes);
2506
- }
2507
- else {
2508
- sub.cacheId = 0;
2509
- }
2510
- }
2511
- else if (!cachedBuffer && shouldCache) {
2512
- this._nodeCache.set(sub.cacheId, packedRes);
2513
- }
2514
- else if (!shouldCache) {
2515
- this._nodeCache.del(sub.cacheId);
2516
- sub.cacheId = 0;
2517
- }
2518
- }
2519
- else {
2520
- const sendStartMs = Date.now();
2521
- let sentClients = 0;
2522
- for (const entry of activeClients) {
2523
- const { client, ws } = entry;
2524
- if (packedRes) {
2525
- this._websocketManager.sendPackedBuffer(
2526
- ws,
2527
- client.messageId,
2528
- false,
2529
- packedRes,
2530
- execution.encoding || 'msgpack',
2531
- { passThrough }
2532
- );
2533
- }
2534
- else {
2535
- let serverRes: ServerResponseModel = {
2536
- messageId: client.messageId,
2537
- hasError: false,
2538
- data: res
2539
- };
2540
-
2541
- this.sendWS(ws, serverRes);
2542
- }
2543
- if (isAiPublication) {
2544
- this.logAiSubSend({
2545
- publication: sub.publication,
2546
- subscriptionKey: sub.subscriptionKey,
2547
- messageId: client.messageId,
2548
- id_socket: ws['id_socket'],
2549
- clients: activeClients.length,
2550
- payloadBytes,
2551
- packed: !!packedRes,
2552
- cacheHit: false,
2553
- collection,
2554
- type,
2555
- subscriptionData: sub.subscriptionData
2556
- });
2557
- }
2558
- sentClients += 1;
2559
- }
2560
- this.logSubSend(sub, {
2561
- collection,
2562
- type,
2563
- clients: sentClients,
2564
- durationMs: Date.now() - sendStartMs,
2565
- payloadBytes
2566
- });
2567
-
2568
- if (shouldCache) {
2569
- sub.cacheId = this._cacheId++;
2570
- this._nodeCache.set(sub.cacheId, packedRes);
2571
-
2572
- const nodeCacheSize = this._nodeCache.getStats().vsize;
2573
-
2574
- if (nodeCacheSize > this._heapLimit) {
2575
- // Evict cache entries as needed
2576
- let deleteCount = 0;
2577
- const subArr = this._subscriptions.filter(a => a.cacheId && !a.clients.length);
2578
-
2579
- for (let zz = 0; zz < subArr.length; zz++) {
2580
- this._debugRemoveCacheHits += 1;
2581
- this._nodeCache.del(subArr[zz].cacheId);
2582
- subArr[zz].cacheId = 0;
2583
- deleteCount += 1;
2584
- if (this._nodeCache.getStats().vsize < this._heapLimit * 0.75) {
2585
- break;
2586
- }
2587
- }
2588
-
2589
- if (this._enableDebug) {
2590
- console.log(
2591
- 'Sub Cache: ' +
2592
- 'Too Big - ' +
2593
- sub.publication +
2594
- ' - Deleted: ' +
2595
- deleteCount +
2596
- ' - ' +
2597
- nodeCacheSize
2598
- );
2599
- }
2600
- }
2601
- }
2602
- else {
2603
- sub.cacheId = 0;
2604
- }
2605
- }
2606
- }
2607
- catch (err) {
2608
- const { error: normalizedError, correlationId } = ensureErrorWithCorrelation(err);
2609
-
2610
- for (let client of sub.clients) {
2611
- let ws = this._websocketManager.getWebSocket(client.id_socket);
2612
- if (ws && ws.readyState === WebSocket.OPEN) {
2613
- let serverRes: ServerResponseModel = {
2614
- messageId: client.messageId,
2615
- hasError: true,
2616
- data: Object.assign({}, normalizedError, { correlationId })
2617
- };
2618
-
2619
- this.sendWS(ws, serverRes);
2620
- }
2621
- }
2622
-
2623
- const errorPayload = {
2624
- publication: sub.publication,
2625
- subscriptionData: sub.subscriptionData,
2626
- type,
2627
- collection,
2628
- correlationId,
2629
- error: {
2630
- name: normalizedError?.name,
2631
- message: normalizedError?.message,
2632
- stack: normalizedError?.stack,
2633
- code: normalizedError?.code,
2634
- codeName: normalizedError?.codeName
2635
- }
2636
- };
2637
-
2638
- await ErrorReporter.report({
2639
- sourceApp: 'subscription-manager',
2640
- message: 'SERVER - Error Detected - ' + this.serverConfig['CLIENT_NAME'],
2641
- environment: this.serverConfig?.ROOT_URL,
2642
- clientSlug: ResolveIOServer.getClientName(),
2643
- clientName: this.serverConfig['CLIENT_NAME'],
2644
- stack: normalizedError?.stack,
2645
- context: errorPayload,
2646
- metadata: {
2647
- context: 'subscription-sendDataToAll',
2648
- publication: sub.publication,
2649
- correlationId
2650
- },
2651
- correlationId
2652
- });
2653
- }
2654
- finally {
2655
- await this._monitorManagerFunction.finishMonitorFunction(monitor);
2656
- }
2657
- }
2658
- }
2659
-
2660
- private ensureDependencyContainers(sub: ActiveSubscriptionModel) {
2661
- if (!sub.collectionDependencies) {
2662
- sub.collectionDependencies = new Map();
2663
- }
2664
-
2665
- if (!sub.collectionFilters) {
2666
- sub.collectionFilters = new Map();
2667
- }
2668
-
2669
- if (!sub.watchAllCollections) {
2670
- sub.watchAllCollections = new Set();
2671
- }
2672
-
2673
- if (!sub.collectionQueryMeta) {
2674
- sub.collectionQueryMeta = new Map();
2675
- }
2676
- }
2677
-
2678
- private updateSubscriptionDependencies(sub: ActiveSubscriptionModel, snapshot?: DependencyContextSnapshot) {
2679
- this.ensureDependencyContainers(sub);
2680
-
2681
- if (!snapshot) {
2682
- sub.collectionDependencies.clear();
2683
- sub.collectionFilters.clear();
2684
- sub.watchAllCollections.clear();
2685
- sub.collectionQueryMeta.clear();
2686
- this.dependencyDebug('Cleared dependency snapshot', { publication: sub.publication });
2687
- return;
2688
- }
2689
-
2690
- sub.collectionDependencies = snapshot.dependencies;
2691
- sub.collectionFilters = snapshot.filters;
2692
- sub.watchAllCollections = snapshot.watchAllCollections;
2693
- sub.collectionQueryMeta = snapshot.queryMetadata;
2694
- this.logDependencySnapshot(sub, 'Snapshot updated');
2695
- }
2696
-
2697
- private normalizeDocumentId(rawId: any): string {
2698
- if (rawId === null || rawId === undefined) {
2699
- return null;
2700
- }
2701
-
2702
- if (typeof rawId === 'string') {
2703
- return rawId;
2704
- }
2705
-
2706
- if (typeof rawId === 'object' && typeof rawId.toHexString === 'function') {
2707
- return rawId.toHexString();
2708
- }
2709
-
2710
- if (typeof rawId?.toString === 'function') {
2711
- const str = rawId.toString();
2712
- if (str && str !== '[object Object]') {
2713
- return str;
2714
- }
2715
- }
2716
-
2717
- return null;
2718
- }
2719
-
2720
- private getDocumentIdQueryCandidates(documentId: any): any[] {
2721
- const candidates: any[] = [];
2722
-
2723
- if (documentId === undefined || documentId === null) {
2724
- return [documentId];
2725
- }
2726
-
2727
- const addCandidate = value => {
2728
- if (value === undefined || value === null) {
2729
- return;
2730
- }
2731
-
2732
- if (!candidates.includes(value)) {
2733
- candidates.push(value);
2734
- }
2735
- };
2736
-
2737
- addCandidate(documentId);
2738
-
2739
- if (typeof documentId === 'object') {
2740
- if (typeof documentId.toHexString === 'function') {
2741
- addCandidate(documentId.toHexString());
2742
- }
2743
-
2744
- if (typeof documentId.toString === 'function') {
2745
- const str = documentId.toString();
2746
- if (str && str !== '[object Object]') {
2747
- addCandidate(str);
2748
- }
2749
- }
2750
- }
2751
- else if (typeof documentId !== 'string') {
2752
- addCandidate(String(documentId));
2753
- }
2754
-
2755
- return candidates;
2756
- }
2757
-
2758
- private isFilterSafeForInMemory(filter: any): boolean {
2759
- if (!filter || typeof filter !== 'object') {
2760
- return false;
2761
- }
2762
-
2763
- const allowedOperators = new Set([
2764
- '$and',
2765
- '$or',
2766
- '$nor',
2767
- '$in',
2768
- '$nin',
2769
- '$eq',
2770
- '$ne',
2771
- '$gt',
2772
- '$gte',
2773
- '$lt',
2774
- '$lte',
2775
- '$exists',
2776
- '$size',
2777
- '$all',
2778
- '$elemMatch',
2779
- '$regex',
2780
- '$options'
2781
- ]);
2782
-
2783
- const disallowedOperators = new Set([
2784
- '$where',
2785
- '$expr',
2786
- '$text',
2787
- '$geoWithin',
2788
- '$geoIntersects',
2789
- '$near',
2790
- '$nearSphere',
2791
- '$jsonSchema',
2792
- '$mod',
2793
- '$type',
2794
- '$bitsAllSet',
2795
- '$bitsAllClear',
2796
- '$bitsAnySet',
2797
- '$bitsAnyClear'
2798
- ]);
2799
-
2800
- const walk = (value: any): boolean => {
2801
- if (Array.isArray(value)) {
2802
- return value.every(item => walk(item));
2803
- }
2804
-
2805
- if (!value || typeof value !== 'object') {
2806
- return true;
2807
- }
2808
-
2809
- for (const [key, entry] of Object.entries(value)) {
2810
- if (key.startsWith('$')) {
2811
- if (disallowedOperators.has(key)) {
2812
- return false;
2813
- }
2814
- if (!allowedOperators.has(key)) {
2815
- return false;
2816
- }
2817
- }
2818
-
2819
- if (!walk(entry)) {
2820
- return false;
2821
- }
2822
- }
2823
-
2824
- return true;
2825
- };
2826
-
2827
- return walk(filter);
2828
- }
2829
-
2830
- private documentIdMatchesEvent(documentId: any, document?: any): boolean {
2831
- if (!document || documentId === undefined || documentId === null) {
2832
- return true;
2833
- }
2834
-
2835
- const eventCandidates = this.getDocumentIdQueryCandidates(documentId)
2836
- .map(id => this.normalizeDocumentId(id))
2837
- .filter(Boolean);
2838
- const docCandidates = this.getDocumentIdQueryCandidates(document?._id)
2839
- .map(id => this.normalizeDocumentId(id))
2840
- .filter(Boolean);
2841
-
2842
- if (!eventCandidates.length || !docCandidates.length) {
2843
- return true;
2844
- }
2845
-
2846
- const docSet = new Set(docCandidates);
2847
-
2848
- for (const candidate of eventCandidates) {
2849
- if (docSet.has(candidate)) {
2850
- return true;
2851
- }
2852
- }
2853
-
2854
- return false;
2855
- }
2856
-
2857
- private resolveFilterMatch(filter: any, document?: any, documentId?: any): boolean | null {
2858
- if (!document || !filter || typeof filter !== 'object') {
2859
- return null;
2860
- }
2861
-
2862
- if (!this.isFilterSafeForInMemory(filter)) {
2863
- return null;
2864
- }
2865
-
2866
- if (!this.documentIdMatchesEvent(documentId, document)) {
2867
- return false;
2868
- }
2869
-
2870
- try {
2871
- const matcher = sift(filter);
2872
- return matcher(document);
2873
- }
2874
- catch {
2875
- return null;
2876
- }
2877
- }
2878
-
2879
- private async documentMatchesFilter(collection: string, documentId: any, filter: any, document?: any): Promise<boolean> {
2880
- try {
2881
- const inMemoryMatch = this.resolveFilterMatch(filter, document, documentId);
2882
- if (inMemoryMatch !== null) {
2883
- return inMemoryMatch;
2884
- }
2885
-
2886
- const db = ResolveIOServer.getMainDB();
2887
- const filterCopy = deepCopy(filter) || {};
2888
- const idCandidates = this.getDocumentIdQueryCandidates(documentId);
2889
-
2890
- for (const idValue of idCandidates) {
2891
- const combinedFilter = {
2892
- $and: [
2893
- { _id: idValue },
2894
- filterCopy
2895
- ]
2896
- };
2897
-
2898
- const doc = await db.collection(collection).findOne(combinedFilter);
2899
- if (doc) {
2900
- return true;
2901
- }
2902
- }
2903
-
2904
- return false;
2905
- }
2906
- catch {
2907
- return true;
2908
- }
2909
- }
2910
-
2911
- private async shouldInvalidateSubscription(sub: ActiveSubscriptionModel, collection: string, type: string, documentId?: any, document?: any): Promise<boolean> {
2912
- this.ensureDependencyContainers(sub);
2913
-
2914
- const normalizedDocumentId = this.normalizeDocumentId(documentId);
2915
- const trackedIds = sub.collectionDependencies.get(collection);
2916
- const filters = sub.collectionFilters.get(collection);
2917
- const hasTrackedIds = !!(trackedIds && trackedIds.size);
2918
- const hasFilters = !!(filters && filters.length);
2919
- const hasDependencyData =
2920
- (sub.collectionDependencies.get(collection)?.size || 0) > 0 ||
2921
- (sub.collectionFilters.get(collection)?.length || 0) > 0 ||
2922
- sub.watchAllCollections.has(collection);
2923
-
2924
- if (!normalizedDocumentId) {
2925
- this.dependencyDebug('Invalidate due to missing documentId', { publication: sub.publication, collection, type });
2926
- return true;
2927
- }
2928
-
2929
- if (!hasDependencyData) {
2930
- this.dependencyDebug('Invalidate due to missing dependency metadata', {
2931
- publication: sub.publication,
2932
- collection,
2933
- type
2934
- });
2935
- return true;
2936
- }
2937
-
2938
- if (sub.watchAllCollections.has(collection)) {
2939
- this.dependencyDebug('Invalidate due to watch-all collection', { publication: sub.publication, collection, type });
2940
- return true;
2941
- }
2942
-
2943
- if (trackedIds && trackedIds.has(normalizedDocumentId)) {
2944
- this.dependencyDebug('Invalidate due to tracked id', { publication: sub.publication, collection, type, documentId: normalizedDocumentId });
2945
- return true;
2946
- }
2947
-
2948
- if (type === 'delete') {
2949
- if (hasFilters && !hasTrackedIds) {
2950
- this.dependencyDebug('Invalidate delete due to filter-only dependency without tracked ids', {
2951
- publication: sub.publication,
2952
- collection,
2953
- type,
2954
- documentId: normalizedDocumentId
2955
- });
2956
- return true;
2957
- }
2958
-
2959
- this.dependencyDebug('Skip invalidation on delete for unknown id', { publication: sub.publication, collection, type, documentId: normalizedDocumentId });
2960
- return false;
2961
- }
2962
-
2963
- if (type === 'update' && hasFilters && !hasTrackedIds) {
2964
- this.dependencyDebug('Invalidate update due to filter-only dependency without tracked ids', {
2965
- publication: sub.publication,
2966
- collection,
2967
- type,
2968
- documentId: normalizedDocumentId
2969
- });
2970
- return true;
2971
- }
2972
-
2973
- if (filters && filters.length) {
2974
- for (const filter of filters) {
2975
- if (await this.documentMatchesFilter(collection, documentId, filter, document)) {
2976
- this.dependencyDebug('Invalidate due to filter match', { publication: sub.publication, collection, type, documentId: normalizedDocumentId, filter });
2977
- return true;
2978
- }
2979
- }
2980
- }
2981
-
2982
- this.dependencyDebug('Skip invalidation after dependency checks', { publication: sub.publication, collection, type, documentId: normalizedDocumentId });
2983
- return false;
2984
- }
2985
-
2986
- private async shouldInvalidateSubscriptionForEvents(sub: ActiveSubscriptionModel, collection: string, events: SubscriptionInvalidationEvent[]): Promise<boolean> {
2987
- let sawInsert = false;
2988
- let sawDelete = false;
2989
-
2990
- for (const event of events) {
2991
- if (event.type === 'insert') {
2992
- sawInsert = true;
2993
- }
2994
- else if (event.type === 'delete') {
2995
- sawDelete = true;
2996
- }
2997
- if (await this.shouldInvalidateSubscription(sub, collection, event.type, event.documentId, event.document)) {
2998
- return true;
2999
- }
3000
- }
3001
-
3002
- const paginationMeta = this.getPaginationMeta(sub, collection);
3003
- const paginationReasons = [];
3004
- if (sawInsert) {
3005
- paginationReasons.push('insert');
3006
- }
3007
- if (sawDelete) {
3008
- paginationReasons.push('delete');
3009
- }
3010
-
3011
- if (paginationMeta?.length && paginationReasons.length) {
3012
- this.dependencyDebug('Invalidate due to pagination metadata', {
3013
- publication: sub.publication,
3014
- collection,
3015
- events: this.summarizeEvents(events),
3016
- reasons: paginationReasons,
3017
- queryMeta: paginationMeta.slice(0, 5)
3018
- });
3019
- return true;
3020
- }
3021
-
3022
- return false;
3023
- }
3024
-
3025
- private getPaginationMeta(sub: ActiveSubscriptionModel, collection: string): QueryMeta[] {
3026
- this.ensureDependencyContainers(sub);
3027
- const metaList = sub.collectionQueryMeta?.get(collection);
3028
- if (!metaList || !metaList.length) {
3029
- return null;
3030
- }
3031
-
3032
- const relevantMeta = metaList.filter(meta => {
3033
- if (!meta) {
3034
- return false;
3035
- }
3036
-
3037
- const limitUsed = typeof meta.limit === 'number' && meta.limit > 0;
3038
- const skipUsed = typeof meta.skip === 'number' && meta.skip > 0;
3039
- return limitUsed || skipUsed;
3040
- });
3041
-
3042
- return relevantMeta.length ? relevantMeta : null;
3043
- }
3044
-
3045
- private sendWS(ws: WebSocket, data: ServerResponseModel) {
3046
- this._websocketManager.send(ws, data);
3047
- }
3048
-
3049
- public getEnableDebug() {
3050
- return this._enableDebug;
3051
- }
3052
-
3053
- private dependencyDebug(message: string, details?: Record<string, unknown>) {
3054
- if (!this._enableDependencyDebug) {
3055
- return;
3056
- }
3057
-
3058
- if (details) {
3059
- console.log(new Date(), '[Dependency Debug]', message, JSON.stringify(details, null, 2));
3060
- }
3061
- else {
3062
- console.log(new Date(), '[Dependency Debug]', message);
3063
- }
3064
- }
3065
-
3066
- private summarizeEvents(events: SubscriptionInvalidationEvent[]) {
3067
- return events.map(event => ({
3068
- type: event.type,
3069
- documentId: this.normalizeDocumentId(event.documentId)
3070
- }));
3071
- }
3072
-
3073
- private logDependencySnapshot(sub: ActiveSubscriptionModel, context: string) {
3074
- if (!this._enableDependencyDebug) {
3075
- return;
3076
- }
3077
-
3078
- const dependencySummary = Array.from(sub.collectionDependencies.entries()).map(([collectionName, ids]) => {
3079
- const idList = Array.from(ids || []);
3080
- return {
3081
- collection: collectionName,
3082
- count: idList.length,
3083
- sample: idList.slice(0, 10)
3084
- };
3085
- });
3086
-
3087
- const filterSummary = Array.from(sub.collectionFilters.entries()).map(([collectionName, filters]) => ({
3088
- collection: collectionName,
3089
- count: filters.length
3090
- }));
3091
-
3092
- const queryMetaSummary = Array.from(sub.collectionQueryMeta.entries()).map(([collectionName, metaList]) => ({
3093
- collection: collectionName,
3094
- count: metaList.length,
3095
- meta: metaList.slice(0, 5)
3096
- }));
3097
-
3098
- const watchAll = Array.from(sub.watchAllCollections || []);
3099
-
3100
- this.dependencyDebug('Dependency snapshot updated', {
3101
- context,
3102
- publication: sub.publication,
3103
- dependencies: dependencySummary,
3104
- collectionsWithFilters: filterSummary,
3105
- collectionsWithMeta: queryMetaSummary,
3106
- watchAll
3107
- });
3108
- }
3109
-
3110
- public async runPublicationOnce(publication: string, subscriptionData: any[] = [], options?: { userId?: string }) {
3111
- const pub = this._publications[publication];
3112
- if (!pub) {
3113
- throw new Error(`Publication not found: ${publication}`);
3114
- }
3115
-
3116
- const context = Object.assign({}, this, SubscriptionManager.prototype);
3117
- const metadata: PublicationContext = {
3118
- publication,
3119
- subscriptionData
3120
- };
3121
-
3122
- if (pub.user_specific) {
3123
- return withDependencyTracking(() => pub.function.call(context, options?.userId || '', ...subscriptionData), metadata);
3124
- }
3125
-
3126
- return withDependencyTracking(() => pub.function.call(context, ...subscriptionData), metadata);
3127
- }
3128
- }