convex 1.34.0 → 1.35.0

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 (607) hide show
  1. package/CHANGELOG.md +98 -43
  2. package/dist/browser.bundle.js +13 -10
  3. package/dist/browser.bundle.js.map +3 -3
  4. package/dist/cjs/browser/index-node.js +3 -1
  5. package/dist/cjs/browser/index.js +3 -1
  6. package/dist/cjs/browser/index.js.map +2 -2
  7. package/dist/cjs/browser/query_options.js.map +2 -2
  8. package/dist/cjs/browser/sync/authentication_manager.js +4 -1
  9. package/dist/cjs/browser/sync/authentication_manager.js.map +2 -2
  10. package/dist/cjs/browser/sync/web_socket_manager.js +1 -7
  11. package/dist/cjs/browser/sync/web_socket_manager.js.map +2 -2
  12. package/dist/cjs/cli/aiFiles.js +39 -20
  13. package/dist/cjs/cli/aiFiles.js.map +3 -3
  14. package/dist/cjs/cli/codegen_templates/readme.js +14 -1
  15. package/dist/cjs/cli/codegen_templates/readme.js.map +2 -2
  16. package/dist/cjs/cli/configure.js +34 -32
  17. package/dist/cjs/cli/configure.js.map +2 -2
  18. package/dist/cjs/cli/deploy.js +7 -8
  19. package/dist/cjs/cli/deploy.js.map +2 -2
  20. package/dist/cjs/cli/deploymentCreate.js +225 -40
  21. package/dist/cjs/cli/deploymentCreate.js.map +3 -3
  22. package/dist/cjs/cli/deploymentSelect.js +14 -13
  23. package/dist/cjs/cli/deploymentSelect.js.map +2 -2
  24. package/dist/cjs/cli/dev.js +30 -11
  25. package/dist/cjs/cli/dev.js.map +2 -2
  26. package/dist/cjs/cli/docs.js +1 -1
  27. package/dist/cjs/cli/docs.js.map +2 -2
  28. package/dist/cjs/cli/init.js +1 -1
  29. package/dist/cjs/cli/init.js.map +2 -2
  30. package/dist/cjs/cli/lib/aiFiles/agentsmd.js +73 -0
  31. package/dist/cjs/cli/lib/aiFiles/agentsmd.js.map +7 -0
  32. package/dist/cjs/cli/lib/aiFiles/claudemd.js +73 -0
  33. package/dist/cjs/cli/lib/aiFiles/claudemd.js.map +7 -0
  34. package/dist/cjs/cli/lib/aiFiles/cursorrules.js +48 -0
  35. package/dist/cjs/cli/lib/aiFiles/cursorrules.js.map +7 -0
  36. package/dist/cjs/cli/lib/aiFiles/guidelinesmd.js +58 -0
  37. package/dist/cjs/cli/lib/aiFiles/guidelinesmd.js.map +7 -0
  38. package/dist/cjs/cli/lib/aiFiles/index.js +215 -0
  39. package/dist/cjs/cli/lib/aiFiles/index.js.map +7 -0
  40. package/dist/cjs/cli/lib/aiFiles/paths.js.map +7 -0
  41. package/dist/cjs/cli/lib/aiFiles/skills.js +196 -0
  42. package/dist/cjs/cli/lib/aiFiles/skills.js.map +7 -0
  43. package/dist/cjs/cli/lib/aiFiles/state.js +96 -0
  44. package/dist/cjs/cli/lib/aiFiles/state.js.map +7 -0
  45. package/dist/cjs/cli/lib/aiFiles/status.js +198 -0
  46. package/dist/cjs/cli/lib/aiFiles/status.js.map +7 -0
  47. package/dist/cjs/cli/lib/aiFiles/utils.js +128 -0
  48. package/dist/cjs/cli/lib/aiFiles/utils.js.map +7 -0
  49. package/dist/cjs/cli/lib/api.js +70 -7
  50. package/dist/cjs/cli/lib/api.js.map +2 -2
  51. package/dist/cjs/cli/lib/command.js +10 -6
  52. package/dist/cjs/cli/lib/command.js.map +2 -2
  53. package/dist/cjs/cli/lib/config.js +43 -7
  54. package/dist/cjs/cli/lib/config.js.map +3 -3
  55. package/dist/cjs/cli/lib/deploy2.js +9 -26
  56. package/dist/cjs/cli/lib/deploy2.js.map +2 -2
  57. package/dist/cjs/cli/lib/deployApi/componentDefinition.js +4 -1
  58. package/dist/cjs/cli/lib/deployApi/componentDefinition.js.map +2 -2
  59. package/dist/cjs/cli/lib/deploymentSelection.js +45 -2
  60. package/dist/cjs/cli/lib/deploymentSelection.js.map +2 -2
  61. package/dist/cjs/cli/lib/deploymentSelector.js +1 -0
  62. package/dist/cjs/cli/lib/deploymentSelector.js.map +2 -2
  63. package/dist/cjs/cli/lib/dev.js +162 -117
  64. package/dist/cjs/cli/lib/dev.js.map +2 -2
  65. package/dist/cjs/cli/lib/env.js +1 -13
  66. package/dist/cjs/cli/lib/env.js.map +2 -2
  67. package/dist/cjs/cli/lib/expiration.js +104 -0
  68. package/dist/cjs/cli/lib/expiration.js.map +7 -0
  69. package/dist/cjs/cli/lib/generatedFunctionLogsApi.js.map +1 -1
  70. package/dist/cjs/cli/lib/init.js +4 -3
  71. package/dist/cjs/cli/lib/init.js.map +2 -2
  72. package/dist/cjs/cli/lib/insights.js +1 -1
  73. package/dist/cjs/cli/lib/insights.js.map +2 -2
  74. package/dist/cjs/cli/lib/localDeployment/anonymous.js +15 -8
  75. package/dist/cjs/cli/lib/localDeployment/anonymous.js.map +2 -2
  76. package/dist/cjs/cli/lib/localDeployment/localDeployment.js +8 -10
  77. package/dist/cjs/cli/lib/localDeployment/localDeployment.js.map +2 -2
  78. package/dist/cjs/cli/lib/localDeployment/run.js +1 -0
  79. package/dist/cjs/cli/lib/localDeployment/run.js.map +2 -2
  80. package/dist/cjs/cli/lib/localDeployment/upgrade.js +2 -2
  81. package/dist/cjs/cli/lib/localDeployment/upgrade.js.map +2 -2
  82. package/dist/cjs/cli/lib/localDeployment/utils.js +9 -0
  83. package/dist/cjs/cli/lib/localDeployment/utils.js.map +2 -2
  84. package/dist/cjs/cli/lib/mcp/tools/status.js +1 -1
  85. package/dist/cjs/cli/lib/mcp/tools/status.js.map +2 -2
  86. package/dist/cjs/cli/lib/updates.js +12 -13
  87. package/dist/cjs/cli/lib/updates.js.map +2 -2
  88. package/dist/cjs/cli/lib/usage.js +2 -1
  89. package/dist/cjs/cli/lib/usage.js.map +2 -2
  90. package/dist/cjs/cli/lib/utils/prompts.js +2 -1
  91. package/dist/cjs/cli/lib/utils/prompts.js.map +2 -2
  92. package/dist/cjs/cli/lib/utils/utils.js +46 -20
  93. package/dist/cjs/cli/lib/utils/utils.js.map +3 -3
  94. package/dist/cjs/cli/lib/versionApi.js +7 -4
  95. package/dist/cjs/cli/lib/versionApi.js.map +2 -2
  96. package/dist/cjs/cli/lib/workos/workos.js +4 -6
  97. package/dist/cjs/cli/lib/workos/workos.js.map +2 -2
  98. package/dist/cjs/index.js +1 -1
  99. package/dist/cjs/index.js.map +1 -1
  100. package/dist/cjs/react/client.js +43 -6
  101. package/dist/cjs/react/client.js.map +2 -2
  102. package/dist/cjs/react/index.js +2 -0
  103. package/dist/cjs/react/index.js.map +2 -2
  104. package/dist/cjs/react-clerk/ConvexProviderWithClerk.js.map +1 -1
  105. package/dist/cjs/server/api.js.map +2 -2
  106. package/dist/cjs/server/components/definition.js.map +1 -1
  107. package/dist/cjs/server/components/index.js +40 -4
  108. package/dist/cjs/server/components/index.js.map +2 -2
  109. package/dist/cjs/server/data_model.js.map +1 -1
  110. package/dist/cjs/server/impl/meta_impl.js +78 -0
  111. package/dist/cjs/server/impl/meta_impl.js.map +7 -0
  112. package/dist/cjs/server/impl/registration_impl.js +16 -11
  113. package/dist/cjs/server/impl/registration_impl.js.map +2 -2
  114. package/dist/cjs/server/index.js.map +2 -2
  115. package/dist/cjs/server/meta.js +17 -0
  116. package/dist/cjs/server/meta.js.map +7 -0
  117. package/dist/cjs/server/registration.js.map +1 -1
  118. package/dist/cjs-types/browser/index.d.ts +1 -0
  119. package/dist/cjs-types/browser/index.d.ts.map +1 -1
  120. package/dist/cjs-types/browser/query_options.d.ts +12 -9
  121. package/dist/cjs-types/browser/query_options.d.ts.map +1 -1
  122. package/dist/cjs-types/browser/sync/authentication_manager.d.ts.map +1 -1
  123. package/dist/cjs-types/browser/sync/web_socket_manager.d.ts.map +1 -1
  124. package/dist/cjs-types/cli/aiFiles.d.ts.map +1 -1
  125. package/dist/cjs-types/cli/codegen_templates/readme.d.ts.map +1 -1
  126. package/dist/cjs-types/cli/configure.d.ts.map +1 -1
  127. package/dist/cjs-types/cli/configure.test.d.ts +2 -0
  128. package/dist/cjs-types/cli/configure.test.d.ts.map +1 -0
  129. package/dist/cjs-types/cli/deploy.d.ts.map +1 -1
  130. package/dist/cjs-types/cli/deploymentCreate.d.ts +1 -0
  131. package/dist/cjs-types/cli/deploymentCreate.d.ts.map +1 -1
  132. package/dist/cjs-types/cli/deploymentSelect.d.ts +2 -1
  133. package/dist/cjs-types/cli/deploymentSelect.d.ts.map +1 -1
  134. package/dist/cjs-types/cli/dev.d.ts +3 -1
  135. package/dist/cjs-types/cli/dev.d.ts.map +1 -1
  136. package/dist/cjs-types/cli/lib/aiFiles/agentsmd.d.ts +19 -0
  137. package/dist/cjs-types/cli/lib/aiFiles/agentsmd.d.ts.map +1 -0
  138. package/dist/cjs-types/cli/lib/aiFiles/agentsmd.test.d.ts +2 -0
  139. package/dist/cjs-types/cli/lib/aiFiles/agentsmd.test.d.ts.map +1 -0
  140. package/dist/cjs-types/cli/lib/aiFiles/claudemd.d.ts +19 -0
  141. package/dist/cjs-types/cli/lib/aiFiles/claudemd.d.ts.map +1 -0
  142. package/dist/cjs-types/cli/lib/aiFiles/claudemd.test.d.ts +2 -0
  143. package/dist/cjs-types/cli/lib/aiFiles/claudemd.test.d.ts.map +1 -0
  144. package/dist/cjs-types/cli/lib/aiFiles/cursorrules.d.ts +10 -0
  145. package/dist/cjs-types/cli/lib/aiFiles/cursorrules.d.ts.map +1 -0
  146. package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.d.ts +12 -0
  147. package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.d.ts.map +1 -0
  148. package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.test.d.ts +2 -0
  149. package/dist/cjs-types/cli/lib/aiFiles/guidelinesmd.test.d.ts.map +1 -0
  150. package/dist/cjs-types/cli/lib/aiFiles/index.d.ts +42 -0
  151. package/dist/cjs-types/cli/lib/aiFiles/index.d.ts.map +1 -0
  152. package/dist/cjs-types/cli/lib/aiFiles/index.test.d.ts.map +1 -0
  153. package/dist/cjs-types/cli/lib/aiFiles/integration.test.d.ts.map +1 -0
  154. package/dist/cjs-types/cli/lib/{ai → aiFiles}/paths.d.ts +4 -0
  155. package/dist/cjs-types/cli/lib/aiFiles/paths.d.ts.map +1 -0
  156. package/dist/cjs-types/cli/lib/aiFiles/prompt.test.d.ts.map +1 -0
  157. package/dist/cjs-types/cli/lib/aiFiles/skills.d.ts +20 -0
  158. package/dist/cjs-types/cli/lib/aiFiles/skills.d.ts.map +1 -0
  159. package/dist/cjs-types/cli/lib/aiFiles/state.d.ts +38 -0
  160. package/dist/cjs-types/cli/lib/aiFiles/state.d.ts.map +1 -0
  161. package/dist/cjs-types/cli/lib/aiFiles/state.test.d.ts +2 -0
  162. package/dist/cjs-types/cli/lib/aiFiles/state.test.d.ts.map +1 -0
  163. package/dist/cjs-types/cli/lib/aiFiles/status.d.ts +6 -0
  164. package/dist/cjs-types/cli/lib/aiFiles/status.d.ts.map +1 -0
  165. package/dist/cjs-types/cli/lib/aiFiles/utils.d.ts +56 -0
  166. package/dist/cjs-types/cli/lib/aiFiles/utils.d.ts.map +1 -0
  167. package/dist/cjs-types/cli/lib/aiFiles/utils.test.d.ts +2 -0
  168. package/dist/cjs-types/cli/lib/aiFiles/utils.test.d.ts.map +1 -0
  169. package/dist/cjs-types/cli/lib/api.d.ts +3 -3
  170. package/dist/cjs-types/cli/lib/api.d.ts.map +1 -1
  171. package/dist/cjs-types/cli/lib/command.d.ts +2 -1
  172. package/dist/cjs-types/cli/lib/command.d.ts.map +1 -1
  173. package/dist/cjs-types/cli/lib/config.d.ts +18 -6
  174. package/dist/cjs-types/cli/lib/config.d.ts.map +1 -1
  175. package/dist/cjs-types/cli/lib/deploy2.d.ts +5 -2
  176. package/dist/cjs-types/cli/lib/deploy2.d.ts.map +1 -1
  177. package/dist/cjs-types/cli/lib/deployApi/componentDefinition.d.ts +27 -12
  178. package/dist/cjs-types/cli/lib/deployApi/componentDefinition.d.ts.map +1 -1
  179. package/dist/cjs-types/cli/lib/deployApi/definitionConfig.d.ts +24 -24
  180. package/dist/cjs-types/cli/lib/deployApi/modules.d.ts +14 -14
  181. package/dist/cjs-types/cli/lib/deployApi/startPush.d.ts +61 -52
  182. package/dist/cjs-types/cli/lib/deployApi/startPush.d.ts.map +1 -1
  183. package/dist/cjs-types/cli/lib/deploymentSelection.d.ts.map +1 -1
  184. package/dist/cjs-types/cli/lib/deploymentSelector.d.ts +2 -0
  185. package/dist/cjs-types/cli/lib/deploymentSelector.d.ts.map +1 -1
  186. package/dist/cjs-types/cli/lib/dev.d.ts.map +1 -1
  187. package/dist/cjs-types/cli/lib/env.d.ts +0 -4
  188. package/dist/cjs-types/cli/lib/env.d.ts.map +1 -1
  189. package/dist/cjs-types/cli/lib/expiration.d.ts +35 -0
  190. package/dist/cjs-types/cli/lib/expiration.d.ts.map +1 -0
  191. package/dist/cjs-types/cli/lib/expiration.test.d.ts +2 -0
  192. package/dist/cjs-types/cli/lib/expiration.test.d.ts.map +1 -0
  193. package/dist/cjs-types/cli/lib/generatedFunctionLogsApi.d.ts +16 -1
  194. package/dist/cjs-types/cli/lib/generatedFunctionLogsApi.d.ts.map +1 -1
  195. package/dist/cjs-types/cli/lib/init.d.ts.map +1 -1
  196. package/dist/cjs-types/cli/lib/localDeployment/anonymous.d.ts.map +1 -1
  197. package/dist/cjs-types/cli/lib/localDeployment/localDeployment.d.ts.map +1 -1
  198. package/dist/cjs-types/cli/lib/localDeployment/run.d.ts +15 -0
  199. package/dist/cjs-types/cli/lib/localDeployment/run.d.ts.map +1 -1
  200. package/dist/cjs-types/cli/lib/localDeployment/upgrade.d.ts.map +1 -1
  201. package/dist/cjs-types/cli/lib/localDeployment/utils.d.ts +7 -0
  202. package/dist/cjs-types/cli/lib/localDeployment/utils.d.ts.map +1 -1
  203. package/dist/cjs-types/cli/lib/mcp/requestContext.d.ts +3 -3
  204. package/dist/cjs-types/cli/lib/mcp/tools/insights.d.ts +2 -2
  205. package/dist/cjs-types/cli/lib/updates.d.ts +4 -3
  206. package/dist/cjs-types/cli/lib/updates.d.ts.map +1 -1
  207. package/dist/cjs-types/cli/lib/usage.d.ts.map +1 -1
  208. package/dist/cjs-types/cli/lib/utils/prompts.d.ts +1 -0
  209. package/dist/cjs-types/cli/lib/utils/prompts.d.ts.map +1 -1
  210. package/dist/cjs-types/cli/lib/utils/utils.d.ts +16 -2
  211. package/dist/cjs-types/cli/lib/utils/utils.d.ts.map +1 -1
  212. package/dist/cjs-types/cli/lib/versionApi.d.ts +7 -1
  213. package/dist/cjs-types/cli/lib/versionApi.d.ts.map +1 -1
  214. package/dist/cjs-types/cli/lib/workos/workos.d.ts.map +1 -1
  215. package/dist/cjs-types/index.d.ts +1 -1
  216. package/dist/cjs-types/react/client.d.ts +54 -2
  217. package/dist/cjs-types/react/client.d.ts.map +1 -1
  218. package/dist/cjs-types/react/index.d.ts +7 -2
  219. package/dist/cjs-types/react/index.d.ts.map +1 -1
  220. package/dist/cjs-types/react/use_query_object_options.test.d.ts +5 -0
  221. package/dist/cjs-types/react/use_query_object_options.test.d.ts.map +1 -0
  222. package/dist/cjs-types/react/use_query_result.test.d.ts +5 -0
  223. package/dist/cjs-types/react/use_query_result.test.d.ts.map +1 -0
  224. package/dist/cjs-types/react-clerk/ConvexProviderWithClerk.d.ts +1 -1
  225. package/dist/cjs-types/server/api.d.ts +5 -1
  226. package/dist/cjs-types/server/api.d.ts.map +1 -1
  227. package/dist/cjs-types/server/components/definition.d.ts +1 -0
  228. package/dist/cjs-types/server/components/definition.d.ts.map +1 -1
  229. package/dist/cjs-types/server/components/index.d.ts +5 -1
  230. package/dist/cjs-types/server/components/index.d.ts.map +1 -1
  231. package/dist/cjs-types/server/data_model.d.ts +2 -1
  232. package/dist/cjs-types/server/data_model.d.ts.map +1 -1
  233. package/dist/cjs-types/server/impl/meta_impl.d.ts +5 -0
  234. package/dist/cjs-types/server/impl/meta_impl.d.ts.map +1 -0
  235. package/dist/cjs-types/server/impl/registration_impl.d.ts.map +1 -1
  236. package/dist/cjs-types/server/index.d.ts +1 -0
  237. package/dist/cjs-types/server/index.d.ts.map +1 -1
  238. package/dist/cjs-types/server/meta.d.ts +72 -0
  239. package/dist/cjs-types/server/meta.d.ts.map +1 -0
  240. package/dist/cjs-types/server/registration.d.ts.map +1 -1
  241. package/dist/cli.bundle.cjs +2446 -1933
  242. package/dist/cli.bundle.cjs.map +4 -4
  243. package/dist/esm/browser/index-node.js +1 -0
  244. package/dist/esm/browser/index.js +1 -0
  245. package/dist/esm/browser/index.js.map +2 -2
  246. package/dist/esm/browser/query_options.js.map +2 -2
  247. package/dist/esm/browser/sync/authentication_manager.js +4 -1
  248. package/dist/esm/browser/sync/authentication_manager.js.map +2 -2
  249. package/dist/esm/browser/sync/web_socket_manager.js +1 -7
  250. package/dist/esm/browser/sync/web_socket_manager.js.map +2 -2
  251. package/dist/esm/cli/aiFiles.js +41 -23
  252. package/dist/esm/cli/aiFiles.js.map +2 -2
  253. package/dist/esm/cli/codegen_templates/readme.js +14 -1
  254. package/dist/esm/cli/codegen_templates/readme.js.map +2 -2
  255. package/dist/esm/cli/configure.js +35 -33
  256. package/dist/esm/cli/configure.js.map +2 -2
  257. package/dist/esm/cli/deploy.js +11 -10
  258. package/dist/esm/cli/deploy.js.map +2 -2
  259. package/dist/esm/cli/deploymentCreate.js +238 -42
  260. package/dist/esm/cli/deploymentCreate.js.map +2 -2
  261. package/dist/esm/cli/deploymentSelect.js +13 -12
  262. package/dist/esm/cli/deploymentSelect.js.map +2 -2
  263. package/dist/esm/cli/dev.js +34 -13
  264. package/dist/esm/cli/dev.js.map +2 -2
  265. package/dist/esm/cli/docs.js +1 -1
  266. package/dist/esm/cli/docs.js.map +2 -2
  267. package/dist/esm/cli/init.js +2 -2
  268. package/dist/esm/cli/init.js.map +2 -2
  269. package/dist/esm/cli/lib/aiFiles/agentsmd.js +56 -0
  270. package/dist/esm/cli/lib/aiFiles/agentsmd.js.map +7 -0
  271. package/dist/esm/cli/lib/aiFiles/claudemd.js +56 -0
  272. package/dist/esm/cli/lib/aiFiles/claudemd.js.map +7 -0
  273. package/dist/esm/cli/lib/aiFiles/cursorrules.js +16 -0
  274. package/dist/esm/cli/lib/aiFiles/cursorrules.js.map +7 -0
  275. package/dist/esm/cli/lib/aiFiles/guidelinesmd.js +35 -0
  276. package/dist/esm/cli/lib/aiFiles/guidelinesmd.js.map +7 -0
  277. package/dist/esm/cli/lib/aiFiles/index.js +193 -0
  278. package/dist/esm/cli/lib/aiFiles/index.js.map +7 -0
  279. package/dist/esm/cli/lib/aiFiles/paths.js.map +7 -0
  280. package/dist/esm/cli/lib/aiFiles/skills.js +163 -0
  281. package/dist/esm/cli/lib/aiFiles/skills.js.map +7 -0
  282. package/dist/esm/cli/lib/aiFiles/state.js +60 -0
  283. package/dist/esm/cli/lib/aiFiles/state.js.map +7 -0
  284. package/dist/esm/cli/lib/aiFiles/status.js +178 -0
  285. package/dist/esm/cli/lib/aiFiles/status.js.map +7 -0
  286. package/dist/esm/cli/lib/aiFiles/utils.js +97 -0
  287. package/dist/esm/cli/lib/aiFiles/utils.js.map +7 -0
  288. package/dist/esm/cli/lib/api.js +70 -7
  289. package/dist/esm/cli/lib/api.js.map +2 -2
  290. package/dist/esm/cli/lib/command.js +10 -6
  291. package/dist/esm/cli/lib/command.js.map +2 -2
  292. package/dist/esm/cli/lib/config.js +41 -6
  293. package/dist/esm/cli/lib/config.js.map +2 -2
  294. package/dist/esm/cli/lib/deploy2.js +13 -26
  295. package/dist/esm/cli/lib/deploy2.js.map +2 -2
  296. package/dist/esm/cli/lib/deployApi/componentDefinition.js +4 -1
  297. package/dist/esm/cli/lib/deployApi/componentDefinition.js.map +2 -2
  298. package/dist/esm/cli/lib/deploymentSelection.js +46 -2
  299. package/dist/esm/cli/lib/deploymentSelection.js.map +2 -2
  300. package/dist/esm/cli/lib/deploymentSelector.js +1 -0
  301. package/dist/esm/cli/lib/deploymentSelector.js.map +2 -2
  302. package/dist/esm/cli/lib/dev.js +162 -118
  303. package/dist/esm/cli/lib/dev.js.map +2 -2
  304. package/dist/esm/cli/lib/env.js +0 -11
  305. package/dist/esm/cli/lib/env.js.map +2 -2
  306. package/dist/esm/cli/lib/expiration.js +80 -0
  307. package/dist/esm/cli/lib/expiration.js.map +7 -0
  308. package/dist/esm/cli/lib/init.js +4 -3
  309. package/dist/esm/cli/lib/init.js.map +2 -2
  310. package/dist/esm/cli/lib/insights.js +1 -1
  311. package/dist/esm/cli/lib/insights.js.map +2 -2
  312. package/dist/esm/cli/lib/localDeployment/anonymous.js +16 -9
  313. package/dist/esm/cli/lib/localDeployment/anonymous.js.map +2 -2
  314. package/dist/esm/cli/lib/localDeployment/localDeployment.js +9 -11
  315. package/dist/esm/cli/lib/localDeployment/localDeployment.js.map +2 -2
  316. package/dist/esm/cli/lib/localDeployment/run.js +1 -1
  317. package/dist/esm/cli/lib/localDeployment/run.js.map +2 -2
  318. package/dist/esm/cli/lib/localDeployment/upgrade.js +2 -2
  319. package/dist/esm/cli/lib/localDeployment/upgrade.js.map +2 -2
  320. package/dist/esm/cli/lib/localDeployment/utils.js +8 -0
  321. package/dist/esm/cli/lib/localDeployment/utils.js.map +2 -2
  322. package/dist/esm/cli/lib/mcp/tools/status.js +1 -1
  323. package/dist/esm/cli/lib/mcp/tools/status.js.map +2 -2
  324. package/dist/esm/cli/lib/updates.js +14 -12
  325. package/dist/esm/cli/lib/updates.js.map +2 -2
  326. package/dist/esm/cli/lib/usage.js +2 -1
  327. package/dist/esm/cli/lib/usage.js.map +2 -2
  328. package/dist/esm/cli/lib/utils/prompts.js +2 -1
  329. package/dist/esm/cli/lib/utils/prompts.js.map +2 -2
  330. package/dist/esm/cli/lib/utils/utils.js +45 -20
  331. package/dist/esm/cli/lib/utils/utils.js.map +3 -3
  332. package/dist/esm/cli/lib/versionApi.js +7 -4
  333. package/dist/esm/cli/lib/versionApi.js.map +2 -2
  334. package/dist/esm/cli/lib/workos/workos.js +4 -6
  335. package/dist/esm/cli/lib/workos/workos.js.map +2 -2
  336. package/dist/esm/index.js +1 -1
  337. package/dist/esm/index.js.map +1 -1
  338. package/dist/esm/react/client.js +43 -6
  339. package/dist/esm/react/client.js.map +2 -2
  340. package/dist/esm/react/index.js +1 -0
  341. package/dist/esm/react/index.js.map +2 -2
  342. package/dist/esm/react-clerk/ConvexProviderWithClerk.js.map +1 -1
  343. package/dist/esm/server/api.js.map +2 -2
  344. package/dist/esm/server/components/index.js +40 -4
  345. package/dist/esm/server/components/index.js.map +2 -2
  346. package/dist/esm/server/impl/meta_impl.js +54 -0
  347. package/dist/esm/server/impl/meta_impl.js.map +7 -0
  348. package/dist/esm/server/impl/registration_impl.js +20 -11
  349. package/dist/esm/server/impl/registration_impl.js.map +2 -2
  350. package/dist/esm/server/index.js.map +2 -2
  351. package/dist/esm/server/meta.js +2 -0
  352. package/dist/esm/server/meta.js.map +7 -0
  353. package/dist/esm-types/browser/index.d.ts +1 -0
  354. package/dist/esm-types/browser/index.d.ts.map +1 -1
  355. package/dist/esm-types/browser/query_options.d.ts +12 -9
  356. package/dist/esm-types/browser/query_options.d.ts.map +1 -1
  357. package/dist/esm-types/browser/sync/authentication_manager.d.ts.map +1 -1
  358. package/dist/esm-types/browser/sync/web_socket_manager.d.ts.map +1 -1
  359. package/dist/esm-types/cli/aiFiles.d.ts.map +1 -1
  360. package/dist/esm-types/cli/codegen_templates/readme.d.ts.map +1 -1
  361. package/dist/esm-types/cli/configure.d.ts.map +1 -1
  362. package/dist/esm-types/cli/configure.test.d.ts +2 -0
  363. package/dist/esm-types/cli/configure.test.d.ts.map +1 -0
  364. package/dist/esm-types/cli/deploy.d.ts.map +1 -1
  365. package/dist/esm-types/cli/deploymentCreate.d.ts +1 -0
  366. package/dist/esm-types/cli/deploymentCreate.d.ts.map +1 -1
  367. package/dist/esm-types/cli/deploymentSelect.d.ts +2 -1
  368. package/dist/esm-types/cli/deploymentSelect.d.ts.map +1 -1
  369. package/dist/esm-types/cli/dev.d.ts +3 -1
  370. package/dist/esm-types/cli/dev.d.ts.map +1 -1
  371. package/dist/esm-types/cli/lib/aiFiles/agentsmd.d.ts +19 -0
  372. package/dist/esm-types/cli/lib/aiFiles/agentsmd.d.ts.map +1 -0
  373. package/dist/esm-types/cli/lib/aiFiles/agentsmd.test.d.ts +2 -0
  374. package/dist/esm-types/cli/lib/aiFiles/agentsmd.test.d.ts.map +1 -0
  375. package/dist/esm-types/cli/lib/aiFiles/claudemd.d.ts +19 -0
  376. package/dist/esm-types/cli/lib/aiFiles/claudemd.d.ts.map +1 -0
  377. package/dist/esm-types/cli/lib/aiFiles/claudemd.test.d.ts +2 -0
  378. package/dist/esm-types/cli/lib/aiFiles/claudemd.test.d.ts.map +1 -0
  379. package/dist/esm-types/cli/lib/aiFiles/cursorrules.d.ts +10 -0
  380. package/dist/esm-types/cli/lib/aiFiles/cursorrules.d.ts.map +1 -0
  381. package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.d.ts +12 -0
  382. package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.d.ts.map +1 -0
  383. package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.test.d.ts +2 -0
  384. package/dist/esm-types/cli/lib/aiFiles/guidelinesmd.test.d.ts.map +1 -0
  385. package/dist/esm-types/cli/lib/aiFiles/index.d.ts +42 -0
  386. package/dist/esm-types/cli/lib/aiFiles/index.d.ts.map +1 -0
  387. package/dist/esm-types/cli/lib/aiFiles/index.test.d.ts.map +1 -0
  388. package/dist/esm-types/cli/lib/aiFiles/integration.test.d.ts.map +1 -0
  389. package/dist/esm-types/cli/lib/{ai → aiFiles}/paths.d.ts +4 -0
  390. package/dist/esm-types/cli/lib/aiFiles/paths.d.ts.map +1 -0
  391. package/dist/esm-types/cli/lib/aiFiles/prompt.test.d.ts.map +1 -0
  392. package/dist/esm-types/cli/lib/aiFiles/skills.d.ts +20 -0
  393. package/dist/esm-types/cli/lib/aiFiles/skills.d.ts.map +1 -0
  394. package/dist/esm-types/cli/lib/aiFiles/state.d.ts +38 -0
  395. package/dist/esm-types/cli/lib/aiFiles/state.d.ts.map +1 -0
  396. package/dist/esm-types/cli/lib/aiFiles/state.test.d.ts +2 -0
  397. package/dist/esm-types/cli/lib/aiFiles/state.test.d.ts.map +1 -0
  398. package/dist/esm-types/cli/lib/aiFiles/status.d.ts +6 -0
  399. package/dist/esm-types/cli/lib/aiFiles/status.d.ts.map +1 -0
  400. package/dist/esm-types/cli/lib/aiFiles/utils.d.ts +56 -0
  401. package/dist/esm-types/cli/lib/aiFiles/utils.d.ts.map +1 -0
  402. package/dist/esm-types/cli/lib/aiFiles/utils.test.d.ts +2 -0
  403. package/dist/esm-types/cli/lib/aiFiles/utils.test.d.ts.map +1 -0
  404. package/dist/esm-types/cli/lib/api.d.ts +3 -3
  405. package/dist/esm-types/cli/lib/api.d.ts.map +1 -1
  406. package/dist/esm-types/cli/lib/command.d.ts +2 -1
  407. package/dist/esm-types/cli/lib/command.d.ts.map +1 -1
  408. package/dist/esm-types/cli/lib/config.d.ts +18 -6
  409. package/dist/esm-types/cli/lib/config.d.ts.map +1 -1
  410. package/dist/esm-types/cli/lib/deploy2.d.ts +5 -2
  411. package/dist/esm-types/cli/lib/deploy2.d.ts.map +1 -1
  412. package/dist/esm-types/cli/lib/deployApi/componentDefinition.d.ts +27 -12
  413. package/dist/esm-types/cli/lib/deployApi/componentDefinition.d.ts.map +1 -1
  414. package/dist/esm-types/cli/lib/deployApi/definitionConfig.d.ts +24 -24
  415. package/dist/esm-types/cli/lib/deployApi/modules.d.ts +14 -14
  416. package/dist/esm-types/cli/lib/deployApi/startPush.d.ts +61 -52
  417. package/dist/esm-types/cli/lib/deployApi/startPush.d.ts.map +1 -1
  418. package/dist/esm-types/cli/lib/deploymentSelection.d.ts.map +1 -1
  419. package/dist/esm-types/cli/lib/deploymentSelector.d.ts +2 -0
  420. package/dist/esm-types/cli/lib/deploymentSelector.d.ts.map +1 -1
  421. package/dist/esm-types/cli/lib/dev.d.ts.map +1 -1
  422. package/dist/esm-types/cli/lib/env.d.ts +0 -4
  423. package/dist/esm-types/cli/lib/env.d.ts.map +1 -1
  424. package/dist/esm-types/cli/lib/expiration.d.ts +35 -0
  425. package/dist/esm-types/cli/lib/expiration.d.ts.map +1 -0
  426. package/dist/esm-types/cli/lib/expiration.test.d.ts +2 -0
  427. package/dist/esm-types/cli/lib/expiration.test.d.ts.map +1 -0
  428. package/dist/esm-types/cli/lib/generatedFunctionLogsApi.d.ts +16 -1
  429. package/dist/esm-types/cli/lib/generatedFunctionLogsApi.d.ts.map +1 -1
  430. package/dist/esm-types/cli/lib/init.d.ts.map +1 -1
  431. package/dist/esm-types/cli/lib/localDeployment/anonymous.d.ts.map +1 -1
  432. package/dist/esm-types/cli/lib/localDeployment/localDeployment.d.ts.map +1 -1
  433. package/dist/esm-types/cli/lib/localDeployment/run.d.ts +15 -0
  434. package/dist/esm-types/cli/lib/localDeployment/run.d.ts.map +1 -1
  435. package/dist/esm-types/cli/lib/localDeployment/upgrade.d.ts.map +1 -1
  436. package/dist/esm-types/cli/lib/localDeployment/utils.d.ts +7 -0
  437. package/dist/esm-types/cli/lib/localDeployment/utils.d.ts.map +1 -1
  438. package/dist/esm-types/cli/lib/mcp/requestContext.d.ts +3 -3
  439. package/dist/esm-types/cli/lib/mcp/tools/insights.d.ts +2 -2
  440. package/dist/esm-types/cli/lib/updates.d.ts +4 -3
  441. package/dist/esm-types/cli/lib/updates.d.ts.map +1 -1
  442. package/dist/esm-types/cli/lib/usage.d.ts.map +1 -1
  443. package/dist/esm-types/cli/lib/utils/prompts.d.ts +1 -0
  444. package/dist/esm-types/cli/lib/utils/prompts.d.ts.map +1 -1
  445. package/dist/esm-types/cli/lib/utils/utils.d.ts +16 -2
  446. package/dist/esm-types/cli/lib/utils/utils.d.ts.map +1 -1
  447. package/dist/esm-types/cli/lib/versionApi.d.ts +7 -1
  448. package/dist/esm-types/cli/lib/versionApi.d.ts.map +1 -1
  449. package/dist/esm-types/cli/lib/workos/workos.d.ts.map +1 -1
  450. package/dist/esm-types/index.d.ts +1 -1
  451. package/dist/esm-types/react/client.d.ts +54 -2
  452. package/dist/esm-types/react/client.d.ts.map +1 -1
  453. package/dist/esm-types/react/index.d.ts +7 -2
  454. package/dist/esm-types/react/index.d.ts.map +1 -1
  455. package/dist/esm-types/react/use_query_object_options.test.d.ts +5 -0
  456. package/dist/esm-types/react/use_query_object_options.test.d.ts.map +1 -0
  457. package/dist/esm-types/react/use_query_result.test.d.ts +5 -0
  458. package/dist/esm-types/react/use_query_result.test.d.ts.map +1 -0
  459. package/dist/esm-types/react-clerk/ConvexProviderWithClerk.d.ts +1 -1
  460. package/dist/esm-types/server/api.d.ts +5 -1
  461. package/dist/esm-types/server/api.d.ts.map +1 -1
  462. package/dist/esm-types/server/components/definition.d.ts +1 -0
  463. package/dist/esm-types/server/components/definition.d.ts.map +1 -1
  464. package/dist/esm-types/server/components/index.d.ts +5 -1
  465. package/dist/esm-types/server/components/index.d.ts.map +1 -1
  466. package/dist/esm-types/server/data_model.d.ts +2 -1
  467. package/dist/esm-types/server/data_model.d.ts.map +1 -1
  468. package/dist/esm-types/server/impl/meta_impl.d.ts +5 -0
  469. package/dist/esm-types/server/impl/meta_impl.d.ts.map +1 -0
  470. package/dist/esm-types/server/impl/registration_impl.d.ts.map +1 -1
  471. package/dist/esm-types/server/index.d.ts +1 -0
  472. package/dist/esm-types/server/index.d.ts.map +1 -1
  473. package/dist/esm-types/server/meta.d.ts +72 -0
  474. package/dist/esm-types/server/meta.d.ts.map +1 -0
  475. package/dist/esm-types/server/registration.d.ts.map +1 -1
  476. package/dist/react.bundle.js +55 -15
  477. package/dist/react.bundle.js.map +3 -3
  478. package/package.json +11 -7
  479. package/schemas/convex.schema.json +22 -3
  480. package/src/browser/index.ts +3 -0
  481. package/src/browser/query_options.test.ts +0 -9
  482. package/src/browser/query_options.ts +36 -15
  483. package/src/browser/sync/authentication_manager.ts +9 -4
  484. package/src/browser/sync/client_node.test.ts +125 -0
  485. package/src/browser/sync/web_socket_manager.ts +1 -7
  486. package/src/cli/aiFiles.ts +56 -33
  487. package/src/cli/codegen_templates/readme.ts +14 -1
  488. package/src/cli/configure.test.ts +138 -0
  489. package/src/cli/configure.ts +62 -55
  490. package/src/cli/deploy.ts +12 -9
  491. package/src/cli/deploymentCreate.test.ts +349 -14
  492. package/src/cli/deploymentCreate.ts +268 -41
  493. package/src/cli/deploymentSelect.test.ts +136 -27
  494. package/src/cli/deploymentSelect.ts +50 -41
  495. package/src/cli/deploymentSelection.test.ts +399 -37
  496. package/src/cli/dev.ts +49 -14
  497. package/src/cli/docs.ts +1 -1
  498. package/src/cli/init.ts +2 -2
  499. package/src/cli/lib/{ai → aiFiles}/MANUAL_TESTING.md +6 -2
  500. package/src/cli/lib/aiFiles/agentsmd.test.ts +133 -0
  501. package/src/cli/lib/aiFiles/agentsmd.ts +81 -0
  502. package/src/cli/lib/aiFiles/claudemd.test.ts +92 -0
  503. package/src/cli/lib/aiFiles/claudemd.ts +81 -0
  504. package/src/cli/lib/aiFiles/cursorrules.ts +25 -0
  505. package/src/cli/lib/aiFiles/guidelinesmd.test.ts +50 -0
  506. package/src/cli/lib/aiFiles/guidelinesmd.ts +49 -0
  507. package/src/cli/lib/{ai → aiFiles}/index.test.ts +343 -516
  508. package/src/cli/lib/aiFiles/index.ts +297 -0
  509. package/src/cli/lib/{ai → aiFiles}/integration.test.ts +195 -158
  510. package/src/cli/lib/{ai → aiFiles}/paths.ts +5 -0
  511. package/src/cli/lib/{ai → aiFiles}/prompt.test.ts +79 -31
  512. package/src/cli/lib/aiFiles/skills.ts +243 -0
  513. package/src/cli/lib/aiFiles/state.test.ts +280 -0
  514. package/src/cli/lib/aiFiles/state.ts +82 -0
  515. package/src/cli/lib/aiFiles/status.ts +246 -0
  516. package/src/cli/lib/aiFiles/utils.test.ts +50 -0
  517. package/src/cli/lib/aiFiles/utils.ts +191 -0
  518. package/src/cli/lib/api.ts +88 -7
  519. package/src/cli/lib/command.ts +18 -8
  520. package/src/cli/lib/config.test.ts +185 -8
  521. package/src/cli/lib/config.ts +73 -12
  522. package/src/cli/lib/deploy2.ts +14 -27
  523. package/src/cli/lib/deployApi/componentDefinition.ts +4 -1
  524. package/src/cli/lib/deploymentSelection.ts +59 -6
  525. package/src/cli/lib/deploymentSelector.test.ts +6 -0
  526. package/src/cli/lib/deploymentSelector.ts +2 -0
  527. package/src/cli/lib/dev.ts +202 -153
  528. package/src/cli/lib/env.ts +0 -15
  529. package/src/cli/lib/expiration.test.ts +159 -0
  530. package/src/cli/lib/expiration.ts +124 -0
  531. package/src/cli/lib/generatedFunctionLogsApi.ts +16 -1
  532. package/src/cli/lib/init.ts +6 -2
  533. package/src/cli/lib/insights.ts +1 -1
  534. package/src/cli/lib/localDeployment/anonymous.ts +19 -9
  535. package/src/cli/lib/localDeployment/localDeployment.ts +9 -11
  536. package/src/cli/lib/localDeployment/run.ts +1 -1
  537. package/src/cli/lib/localDeployment/upgrade.ts +12 -10
  538. package/src/cli/lib/localDeployment/utils.ts +12 -0
  539. package/src/cli/lib/mcp/tools/status.ts +1 -1
  540. package/src/cli/lib/updates.test.ts +97 -60
  541. package/src/cli/lib/updates.ts +17 -15
  542. package/src/cli/lib/usage.ts +3 -1
  543. package/src/cli/lib/utils/prompts.ts +2 -0
  544. package/src/cli/lib/utils/utils.test.ts +6 -6
  545. package/src/cli/lib/utils/utils.ts +66 -27
  546. package/src/cli/lib/versionApi.test.ts +13 -10
  547. package/src/cli/lib/versionApi.ts +13 -5
  548. package/src/cli/lib/workos/workos.ts +4 -5
  549. package/src/index.ts +1 -1
  550. package/src/react/client.test.tsx +65 -0
  551. package/src/react/client.ts +129 -13
  552. package/src/react/index.ts +9 -1
  553. package/src/react/use_query_object_options.test.ts +50 -0
  554. package/src/react/use_query_result.test.ts +41 -0
  555. package/src/react-clerk/ConvexProviderWithClerk.test.tsx +1 -1
  556. package/src/react-clerk/ConvexProviderWithClerk.tsx +1 -1
  557. package/src/server/api.ts +5 -1
  558. package/src/server/components/definition.ts +3 -0
  559. package/src/server/components/index.ts +62 -5
  560. package/src/server/data_model.ts +2 -1
  561. package/src/server/impl/meta_impl.ts +74 -0
  562. package/src/server/impl/registration_impl.ts +21 -9
  563. package/src/server/index.ts +8 -0
  564. package/src/server/meta.ts +76 -0
  565. package/src/server/registration.ts +10 -0
  566. package/src/server/schema.test.ts +78 -1
  567. package/dist/cjs/cli/lib/ai/config.js +0 -144
  568. package/dist/cjs/cli/lib/ai/config.js.map +0 -7
  569. package/dist/cjs/cli/lib/ai/index.js +0 -704
  570. package/dist/cjs/cli/lib/ai/index.js.map +0 -7
  571. package/dist/cjs/cli/lib/ai/paths.js.map +0 -7
  572. package/dist/cjs-types/cli/lib/ai/config.d.ts +0 -50
  573. package/dist/cjs-types/cli/lib/ai/config.d.ts.map +0 -1
  574. package/dist/cjs-types/cli/lib/ai/config.test.d.ts +0 -2
  575. package/dist/cjs-types/cli/lib/ai/config.test.d.ts.map +0 -1
  576. package/dist/cjs-types/cli/lib/ai/index.d.ts +0 -56
  577. package/dist/cjs-types/cli/lib/ai/index.d.ts.map +0 -1
  578. package/dist/cjs-types/cli/lib/ai/index.test.d.ts.map +0 -1
  579. package/dist/cjs-types/cli/lib/ai/integration.test.d.ts.map +0 -1
  580. package/dist/cjs-types/cli/lib/ai/paths.d.ts.map +0 -1
  581. package/dist/cjs-types/cli/lib/ai/prompt.test.d.ts.map +0 -1
  582. package/dist/esm/cli/lib/ai/config.js +0 -109
  583. package/dist/esm/cli/lib/ai/config.js.map +0 -7
  584. package/dist/esm/cli/lib/ai/index.js +0 -684
  585. package/dist/esm/cli/lib/ai/index.js.map +0 -7
  586. package/dist/esm/cli/lib/ai/paths.js.map +0 -7
  587. package/dist/esm-types/cli/lib/ai/config.d.ts +0 -50
  588. package/dist/esm-types/cli/lib/ai/config.d.ts.map +0 -1
  589. package/dist/esm-types/cli/lib/ai/config.test.d.ts +0 -2
  590. package/dist/esm-types/cli/lib/ai/config.test.d.ts.map +0 -1
  591. package/dist/esm-types/cli/lib/ai/index.d.ts +0 -56
  592. package/dist/esm-types/cli/lib/ai/index.d.ts.map +0 -1
  593. package/dist/esm-types/cli/lib/ai/index.test.d.ts.map +0 -1
  594. package/dist/esm-types/cli/lib/ai/integration.test.d.ts.map +0 -1
  595. package/dist/esm-types/cli/lib/ai/paths.d.ts.map +0 -1
  596. package/dist/esm-types/cli/lib/ai/prompt.test.d.ts.map +0 -1
  597. package/src/cli/lib/ai/config.test.ts +0 -338
  598. package/src/cli/lib/ai/config.ts +0 -159
  599. package/src/cli/lib/ai/index.ts +0 -1006
  600. /package/dist/cjs/cli/lib/{ai → aiFiles}/paths.js +0 -0
  601. /package/dist/cjs-types/cli/lib/{ai → aiFiles}/index.test.d.ts +0 -0
  602. /package/dist/cjs-types/cli/lib/{ai → aiFiles}/integration.test.d.ts +0 -0
  603. /package/dist/cjs-types/cli/lib/{ai → aiFiles}/prompt.test.d.ts +0 -0
  604. /package/dist/esm/cli/lib/{ai → aiFiles}/paths.js +0 -0
  605. /package/dist/esm-types/cli/lib/{ai → aiFiles}/index.test.d.ts +0 -0
  606. /package/dist/esm-types/cli/lib/{ai → aiFiles}/integration.test.d.ts +0 -0
  607. /package/dist/esm-types/cli/lib/{ai → aiFiles}/prompt.test.d.ts +0 -0
@@ -2,4 +2,5 @@
2
2
  export { BaseConvexClient } from "./sync/client.js";
3
3
  export { ConvexClient } from "./simple_client-node.js";
4
4
  export { ConvexHttpClient } from "./http_client.js";
5
+ export { convexQueryOptions } from "./query_options.js";
5
6
  //# sourceMappingURL=index.js.map
@@ -2,4 +2,5 @@
2
2
  export { BaseConvexClient } from "./sync/client.js";
3
3
  export { ConvexClient } from "./simple_client.js";
4
4
  export { ConvexHttpClient } from "./http_client.js";
5
+ export { convexQueryOptions } from "./query_options.js";
5
6
  //# sourceMappingURL=index.js.map
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/browser/index.ts"],
4
- "sourcesContent": ["/**\n * Tools for accessing Convex in the browser.\n *\n * **If you are using React, use the {@link react} module instead.**\n *\n * ## Usage\n *\n * Create a {@link ConvexHttpClient} to connect to the Convex Cloud.\n *\n * ```typescript\n * import { ConvexHttpClient } from \"convex/browser\";\n * // typically loaded from an environment variable\n * const address = \"https://small-mouse-123.convex.cloud\";\n * const convex = new ConvexHttpClient(address);\n * ```\n *\n * @module\n */\nexport { BaseConvexClient } from \"./sync/client.js\";\nexport type {\n BaseConvexClientOptions,\n MutationOptions,\n SubscribeOptions,\n ConnectionState,\n AuthTokenFetcher,\n} from \"./sync/client.js\";\nexport type { PaginationStatus } from \"./sync/pagination.js\";\nexport type { ConvexClientOptions } from \"./simple_client.js\";\nexport { ConvexClient } from \"./simple_client.js\";\nexport type {\n OptimisticUpdate,\n OptimisticLocalStore,\n} from \"./sync/optimistic_updates.js\";\nexport type { QueryToken } from \"./sync/udf_path_utils.js\";\n/** @internal */\nexport type { PaginatedQueryToken } from \"./sync/udf_path_utils.js\";\nexport { ConvexHttpClient } from \"./http_client.js\";\nexport type { HttpMutationOptions } from \"./http_client.js\";\nexport type { QueryJournal } from \"./sync/protocol.js\";\n/** @internal */\nexport type { UserIdentityAttributes } from \"./sync/protocol.js\";\nexport type { FunctionResult } from \"./sync/function_result.js\";\n"],
5
- "mappings": ";AAkBA,SAAS,wBAAwB;AAUjC,SAAS,oBAAoB;AAQ7B,SAAS,wBAAwB;",
4
+ "sourcesContent": ["/**\n * Tools for accessing Convex in the browser.\n *\n * **If you are using React, use the {@link react} module instead.**\n *\n * ## Usage\n *\n * Create a {@link ConvexHttpClient} to connect to the Convex Cloud.\n *\n * ```typescript\n * import { ConvexHttpClient } from \"convex/browser\";\n * // typically loaded from an environment variable\n * const address = \"https://small-mouse-123.convex.cloud\";\n * const convex = new ConvexHttpClient(address);\n * ```\n *\n * @module\n */\nexport { BaseConvexClient } from \"./sync/client.js\";\nexport type {\n BaseConvexClientOptions,\n MutationOptions,\n SubscribeOptions,\n ConnectionState,\n AuthTokenFetcher,\n} from \"./sync/client.js\";\nexport type { PaginationStatus } from \"./sync/pagination.js\";\nexport type { ConvexClientOptions } from \"./simple_client.js\";\nexport { ConvexClient } from \"./simple_client.js\";\nexport type {\n OptimisticUpdate,\n OptimisticLocalStore,\n} from \"./sync/optimistic_updates.js\";\nexport type { QueryToken } from \"./sync/udf_path_utils.js\";\n/** @internal */\nexport type { PaginatedQueryToken } from \"./sync/udf_path_utils.js\";\nexport { ConvexHttpClient } from \"./http_client.js\";\nexport type { HttpMutationOptions } from \"./http_client.js\";\nexport type { QueryJournal } from \"./sync/protocol.js\";\n/** @internal */\nexport type { UserIdentityAttributes } from \"./sync/protocol.js\";\nexport type { FunctionResult } from \"./sync/function_result.js\";\n/** @internal */\nexport { convexQueryOptions } from \"./query_options.js\";\nexport type { QueryOptions } from \"./query_options.js\";\n"],
5
+ "mappings": ";AAkBA,SAAS,wBAAwB;AAUjC,SAAS,oBAAoB;AAQ7B,SAAS,wBAAwB;AAOjC,SAAS,0BAA0B;",
6
6
  "names": []
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../src/browser/query_options.ts"],
4
- "sourcesContent": ["/**\n * Query options are a potential new API for a variety of functions, but in particular a new overload of the React hook for queries.\n *\n * Inspired by https://tanstack.com/query/v5/docs/framework/react/guides/query-options\n */\nimport type { FunctionArgs, FunctionReference } from \"../server/api.js\";\n\n// TODO if this type can encompass all use cases we can add not requiring args for queries\n// that don't take arguments. Goal would be that queryOptions allows leaving out args,\n// but queryOptions returns an object that always contains args. Helpers, \"middleware,\"\n// anything that intercepts these arguments\n/**\n * Query options.\n */\nexport type ConvexQueryOptions<Query extends FunctionReference<\"query\">> = {\n query: Query;\n args: FunctionArgs<Query>;\n extendSubscriptionFor?: number;\n};\n\n// This helper helps more once we have more inference happening.\nexport function convexQueryOptions<Query extends FunctionReference<\"query\">>(\n options: ConvexQueryOptions<Query>,\n): ConvexQueryOptions<Query> {\n return options;\n}\n"],
5
- "mappings": ";AAqBO,gBAAS,mBACd,SAC2B;AAC3B,SAAO;AACT;",
4
+ "sourcesContent": ["// Inspired by https://tanstack.com/query/v5/docs/framework/react/guides/query-options\nimport type { FunctionArgs, FunctionReference } from \"../server/api.js\";\n\n/**\n * Options for a Convex query: the query function reference and its arguments.\n *\n * Used with the object-form overload of {@link useQuery}.\n *\n * @public\n */\nexport type QueryOptions<Query extends FunctionReference<\"query\">> = {\n /**\n * The query function to run.\n */\n query: Query;\n /**\n * The arguments to the query function.\n */\n args: FunctionArgs<Query>;\n};\n\n/**\n * Creates a type-safe {@link QueryOptions} object for a Convex query.\n *\n * This is an identity function that exists to provide type inference \u2014 passing\n * your query and args through this helper ensures TypeScript infers the correct\n * `Query` type parameter, which enables precise return types on hooks like\n * {@link useQuery}.\n *\n * ```typescript\n * const opts = convexQueryOptions({\n * query: api.users.getById,\n * args: { id: userId },\n * });\n * // opts is typed as QueryOptions<typeof api.users.getById>\n * client.prewarmQuery(opts);\n * ```\n *\n * @param options - The query and its arguments.\n * @returns The same object, typed as `QueryOptions<Query>`.\n * @internal\n */\nexport function convexQueryOptions<Query extends FunctionReference<\"query\">>(\n options: QueryOptions<Query>,\n): QueryOptions<Query> {\n return options;\n}\n"],
5
+ "mappings": ";AA0CO,gBAAS,mBACd,SACqB;AACrB,SAAO;AACT;",
6
6
  "names": []
7
7
  }
@@ -72,6 +72,10 @@ export class AuthenticationManager {
72
72
  if (serverMessage.endVersion.identity <= serverMessage.startVersion.identity) {
73
73
  return;
74
74
  }
75
+ this._logVerbose(
76
+ `auth state is ${this.authState.state} when handling transition`
77
+ );
78
+ this.syncState.markAuthCompletion();
75
79
  if (this.authState.state === "waitingForServerConfirmationOfCachedToken") {
76
80
  this._logVerbose("server confirmed auth token is valid");
77
81
  void this.refetchToken();
@@ -301,7 +305,6 @@ export class AuthenticationManager {
301
305
  }
302
306
  if (this.authState.state === "waitingForScheduledRefetch") {
303
307
  clearTimeout(this.authState.refetchTokenTimeoutId);
304
- this.syncState.markAuthCompletion();
305
308
  }
306
309
  this.authState = newAuth;
307
310
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/browser/sync/authentication_manager.ts"],
4
- "sourcesContent": ["import { Logger } from \"../logging.js\";\nimport { LocalSyncState } from \"./local_state.js\";\nimport { AuthError, IdentityVersion, Transition } from \"./protocol.js\";\nimport { jwtDecode } from \"../../vendor/jwt-decode/index.js\";\n\n// setTimout uses 32 bit integer, so it can only\n// schedule about 24 days in the future.\nconst MAXIMUM_REFRESH_DELAY = 20 * 24 * 60 * 60 * 1000; // 20 days\n\nconst MAX_TOKEN_CONFIRMATION_ATTEMPTS = 2;\n\n/**\n * An async function returning a JWT. Depending on the auth providers\n * configured in convex/auth.config.ts, this may be a JWT-encoded OpenID\n * Connect Identity Token or a traditional JWT.\n *\n * `forceRefreshToken` is `true` if the server rejected a previously\n * returned token or the token is anticipated to expiring soon\n * based on its `exp` time.\n *\n * See {@link ConvexReactClient.setAuth}.\n *\n * @public\n */\nexport type AuthTokenFetcher = (args: {\n forceRefreshToken: boolean;\n}) => Promise<string | null | undefined>;\n\n/**\n * What is provided to the client.\n */\ntype AuthConfig = {\n fetchToken: AuthTokenFetcher;\n onAuthChange: (isAuthenticated: boolean) => void;\n};\n\n/**\n * In general we take 3 steps:\n * 1. Fetch a possibly cached token\n * 2. Immediately fetch a fresh token without using a cache\n * 3. Repeat step 2 before the end of the fresh token's lifetime\n *\n * When we fetch without using a cache we know when the token\n * will expire, and can schedule refetching it.\n *\n * If we get an error before a scheduled refetch, we go back\n * to step 2.\n */\ntype AuthState =\n | { state: \"noAuth\" }\n | {\n state: \"waitingForServerConfirmationOfCachedToken\";\n config: AuthConfig;\n hasRetried: boolean;\n }\n | {\n state: \"initialRefetch\";\n config: AuthConfig;\n }\n | {\n state: \"waitingForServerConfirmationOfFreshToken\";\n config: AuthConfig;\n hadAuth: boolean;\n token: string;\n }\n | {\n state: \"waitingForScheduledRefetch\";\n config: AuthConfig;\n refetchTokenTimeoutId: ReturnType<typeof setTimeout>;\n }\n // Special/weird state when we got a valid token\n // but could not fetch a new one.\n | {\n state: \"notRefetching\";\n config: AuthConfig;\n };\n\n/**\n * Handles the state transitions for auth. The server is the source\n * of truth.\n */\nexport class AuthenticationManager {\n private authState: AuthState = { state: \"noAuth\" };\n // Used to detect races involving `setConfig` calls\n // while a token is being fetched.\n private configVersion = 0;\n // Shared by the BaseClient so that the auth manager can easily inspect it\n private readonly syncState: LocalSyncState;\n // Passed down by BaseClient, sends a message to the server\n private readonly authenticate: (token: string) => IdentityVersion;\n private readonly stopSocket: () => Promise<void>;\n private readonly tryRestartSocket: () => void;\n private readonly pauseSocket: () => void;\n private readonly resumeSocket: () => void;\n // Passed down by BaseClient, sends a message to the server\n private readonly clearAuth: () => void;\n private readonly logger: Logger;\n private readonly refreshTokenLeewaySeconds: number;\n // Number of times we have attempted to confirm the latest token. We retry up\n // to `MAX_TOKEN_CONFIRMATION_ATTEMPTS` times.\n private tokenConfirmationAttempts = 0;\n constructor(\n syncState: LocalSyncState,\n callbacks: {\n authenticate: (token: string) => IdentityVersion;\n stopSocket: () => Promise<void>;\n tryRestartSocket: () => void;\n pauseSocket: () => void;\n resumeSocket: () => void;\n clearAuth: () => void;\n },\n config: {\n refreshTokenLeewaySeconds: number;\n logger: Logger;\n },\n ) {\n this.syncState = syncState;\n this.authenticate = callbacks.authenticate;\n this.stopSocket = callbacks.stopSocket;\n this.tryRestartSocket = callbacks.tryRestartSocket;\n this.pauseSocket = callbacks.pauseSocket;\n this.resumeSocket = callbacks.resumeSocket;\n this.clearAuth = callbacks.clearAuth;\n this.logger = config.logger;\n this.refreshTokenLeewaySeconds = config.refreshTokenLeewaySeconds;\n }\n\n async setConfig(\n fetchToken: AuthTokenFetcher,\n onChange: (isAuthenticated: boolean) => void,\n ) {\n this.resetAuthState();\n this._logVerbose(\"pausing WS for auth token fetch\");\n this.pauseSocket();\n const token = await this.fetchTokenAndGuardAgainstRace(fetchToken, {\n forceRefreshToken: false,\n });\n if (token.isFromOutdatedConfig) {\n return;\n }\n if (token.value) {\n this.setAuthState({\n state: \"waitingForServerConfirmationOfCachedToken\",\n config: { fetchToken, onAuthChange: onChange },\n hasRetried: false,\n });\n this.authenticate(token.value);\n } else {\n this.setAuthState({\n state: \"initialRefetch\",\n config: { fetchToken, onAuthChange: onChange },\n });\n // Try again with `forceRefreshToken: true`\n await this.refetchToken();\n }\n this._logVerbose(\"resuming WS after auth token fetch\");\n this.resumeSocket();\n }\n\n onTransition(serverMessage: Transition) {\n if (\n !this.syncState.isCurrentOrNewerAuthVersion(\n serverMessage.endVersion.identity,\n )\n ) {\n // This is a stale transition - client has moved on to\n // a newer auth version.\n return;\n }\n if (\n serverMessage.endVersion.identity <= serverMessage.startVersion.identity\n ) {\n // This transition did not change auth - it is not a response to Authenticate.\n return;\n }\n\n if (this.authState.state === \"waitingForServerConfirmationOfCachedToken\") {\n this._logVerbose(\"server confirmed auth token is valid\");\n void this.refetchToken();\n this.authState.config.onAuthChange(true);\n return;\n }\n if (this.authState.state === \"waitingForServerConfirmationOfFreshToken\") {\n this._logVerbose(\"server confirmed new auth token is valid\");\n this.scheduleTokenRefetch(this.authState.token);\n this.tokenConfirmationAttempts = 0;\n if (!this.authState.hadAuth) {\n this.authState.config.onAuthChange(true);\n }\n }\n }\n\n onAuthError(serverMessage: AuthError) {\n // If the AuthError is not due to updating the token, and we're currently\n // waiting on the result of a token update, ignore.\n if (\n serverMessage.authUpdateAttempted === false &&\n (this.authState.state === \"waitingForServerConfirmationOfFreshToken\" ||\n this.authState.state === \"waitingForServerConfirmationOfCachedToken\")\n ) {\n this._logVerbose(\"ignoring non-auth token expired error\");\n return;\n }\n const { baseVersion } = serverMessage;\n // Versioned AuthErrors are ignored if the client advanced to\n // a newer auth identity\n // Error are reporting the previous version, since the server\n // didn't advance, hence `+ 1`.\n if (!this.syncState.isCurrentOrNewerAuthVersion(baseVersion + 1)) {\n this._logVerbose(\"ignoring auth error for previous auth attempt\");\n return;\n }\n void this.tryToReauthenticate(serverMessage);\n return;\n }\n\n // This is similar to `refetchToken` defined below, in fact we\n // don't represent them as different states, but it is different\n // in that we pause the WebSocket so that mutations\n // don't retry with bad auth.\n private async tryToReauthenticate(serverMessage: AuthError) {\n this._logVerbose(`attempting to reauthenticate: ${serverMessage.error}`);\n if (\n // No way to fetch another token, kaboom\n this.authState.state === \"noAuth\" ||\n // We failed on a fresh token. After a small number of retries, we give up\n // and clear the auth state to avoid infinite retries.\n (this.authState.state === \"waitingForServerConfirmationOfFreshToken\" &&\n this.tokenConfirmationAttempts >= MAX_TOKEN_CONFIRMATION_ATTEMPTS)\n ) {\n this.logger.error(\n `Failed to authenticate: \"${serverMessage.error}\", check your server auth config`,\n );\n if (this.syncState.hasAuth()) {\n this.syncState.clearAuth();\n }\n if (this.authState.state !== \"noAuth\") {\n this.setAndReportAuthFailed(this.authState.config.onAuthChange);\n }\n return;\n }\n if (this.authState.state === \"waitingForServerConfirmationOfFreshToken\") {\n this.tokenConfirmationAttempts++;\n this._logVerbose(\n `retrying reauthentication, ${MAX_TOKEN_CONFIRMATION_ATTEMPTS - this.tokenConfirmationAttempts} attempts remaining`,\n );\n }\n\n await this.stopSocket();\n const token = await this.fetchTokenAndGuardAgainstRace(\n this.authState.config.fetchToken,\n {\n forceRefreshToken: true,\n },\n );\n if (token.isFromOutdatedConfig) {\n return;\n }\n\n if (token.value && this.syncState.isNewAuth(token.value)) {\n this.authenticate(token.value);\n this.setAuthState({\n state: \"waitingForServerConfirmationOfFreshToken\",\n config: this.authState.config,\n token: token.value,\n hadAuth:\n this.authState.state === \"notRefetching\" ||\n this.authState.state === \"waitingForScheduledRefetch\",\n });\n } else {\n this._logVerbose(\"reauthentication failed, could not fetch a new token\");\n if (this.syncState.hasAuth()) {\n this.syncState.clearAuth();\n }\n this.setAndReportAuthFailed(this.authState.config.onAuthChange);\n }\n this.tryRestartSocket();\n }\n\n // Force refetch the token and schedule another refetch\n // before the token expires - an active client should never\n // need to reauthenticate.\n private async refetchToken() {\n if (this.authState.state === \"noAuth\") {\n return;\n }\n this._logVerbose(\"refetching auth token\");\n const token = await this.fetchTokenAndGuardAgainstRace(\n this.authState.config.fetchToken,\n {\n forceRefreshToken: true,\n },\n );\n if (token.isFromOutdatedConfig) {\n return;\n }\n\n if (token.value) {\n if (this.syncState.isNewAuth(token.value)) {\n this.setAuthState({\n state: \"waitingForServerConfirmationOfFreshToken\",\n hadAuth: this.syncState.hasAuth(),\n token: token.value,\n config: this.authState.config,\n });\n this.authenticate(token.value);\n } else {\n this.setAuthState({\n state: \"notRefetching\",\n config: this.authState.config,\n });\n }\n } else {\n this._logVerbose(\"refetching token failed\");\n if (this.syncState.hasAuth()) {\n this.clearAuth();\n }\n this.setAndReportAuthFailed(this.authState.config.onAuthChange);\n }\n // Restart in case this refetch was triggered via schedule during\n // a reauthentication attempt.\n this._logVerbose(\n \"restarting WS after auth token fetch (if currently stopped)\",\n );\n this.tryRestartSocket();\n }\n\n private scheduleTokenRefetch(token: string) {\n if (this.authState.state === \"noAuth\") {\n return;\n }\n const decodedToken = this.decodeToken(token);\n if (!decodedToken) {\n // This is no longer really possible, because\n // we wait on server response before scheduling token refetch,\n // and the server currently requires JWT tokens.\n this.logger.error(\n \"Auth token is not a valid JWT, cannot refetch the token\",\n );\n return;\n }\n // iat: issued at time, UTC seconds timestamp at which the JWT was issued\n // exp: expiration time, UTC seconds timestamp at which the JWT will expire\n const { iat, exp } = decodedToken as { iat?: number; exp?: number };\n if (!iat || !exp) {\n this.logger.error(\n \"Auth token does not have required fields, cannot refetch the token\",\n );\n return;\n }\n // Because the client and server clocks may be out of sync,\n // we only know that the token will expire after `exp - iat`,\n // and since we just fetched a fresh one we know when that\n // will happen.\n const tokenValiditySeconds = exp - iat;\n if (tokenValiditySeconds <= 2) {\n this.logger.error(\n \"Auth token does not live long enough, cannot refetch the token\",\n );\n return;\n }\n // Attempt to refresh the token `refreshTokenLeewaySeconds` before it expires,\n // or immediately if the token is already expiring soon.\n let delay = Math.min(\n MAXIMUM_REFRESH_DELAY,\n (tokenValiditySeconds - this.refreshTokenLeewaySeconds) * 1000,\n );\n if (delay <= 0) {\n // Refetch immediately, but this might be due to configuring a `refreshTokenLeewaySeconds`\n // that is too large compared to the token's actual lifetime.\n this.logger.warn(\n `Refetching auth token immediately, configured leeway ${this.refreshTokenLeewaySeconds}s is larger than the token's lifetime ${tokenValiditySeconds}s`,\n );\n delay = 0;\n }\n const refetchTokenTimeoutId = setTimeout(() => {\n this._logVerbose(\"running scheduled token refetch\");\n void this.refetchToken();\n }, delay);\n this.setAuthState({\n state: \"waitingForScheduledRefetch\",\n refetchTokenTimeoutId,\n config: this.authState.config,\n });\n this._logVerbose(\n `scheduled preemptive auth token refetching in ${delay}ms`,\n );\n }\n\n // Protects against simultaneous calls to `setConfig`\n // while we're fetching a token\n private async fetchTokenAndGuardAgainstRace(\n fetchToken: AuthTokenFetcher,\n fetchArgs: {\n forceRefreshToken: boolean;\n },\n ) {\n const originalConfigVersion = ++this.configVersion;\n this._logVerbose(\n `fetching token with config version ${originalConfigVersion}`,\n );\n const token = await fetchToken(fetchArgs);\n if (this.configVersion !== originalConfigVersion) {\n // This is a stale config\n this._logVerbose(\n `stale config version, expected ${originalConfigVersion}, got ${this.configVersion}`,\n );\n return { isFromOutdatedConfig: true };\n }\n return { isFromOutdatedConfig: false, value: token };\n }\n\n stop() {\n this.resetAuthState();\n // Bump this in case we are mid-token-fetch when we get stopped\n this.configVersion++;\n this._logVerbose(`config version bumped to ${this.configVersion}`);\n }\n\n private setAndReportAuthFailed(\n onAuthChange: (authenticated: boolean) => void,\n ) {\n onAuthChange(false);\n this.resetAuthState();\n }\n\n private resetAuthState() {\n this.setAuthState({ state: \"noAuth\" });\n }\n\n private setAuthState(newAuth: AuthState) {\n const authStateForLog =\n newAuth.state === \"waitingForServerConfirmationOfFreshToken\"\n ? {\n hadAuth: newAuth.hadAuth,\n state: newAuth.state,\n token: `...${newAuth.token.slice(-7)}`,\n }\n : { state: newAuth.state };\n this._logVerbose(\n `setting auth state to ${JSON.stringify(authStateForLog)}`,\n );\n switch (newAuth.state) {\n case \"waitingForScheduledRefetch\":\n case \"notRefetching\":\n case \"noAuth\":\n this.tokenConfirmationAttempts = 0;\n break;\n case \"waitingForServerConfirmationOfFreshToken\":\n case \"waitingForServerConfirmationOfCachedToken\":\n case \"initialRefetch\":\n break;\n default: {\n newAuth satisfies never;\n }\n }\n if (this.authState.state === \"waitingForScheduledRefetch\") {\n clearTimeout(this.authState.refetchTokenTimeoutId);\n\n // The waitingForScheduledRefetch state is the most quiesced authed state.\n // Let the syncState know that auth is in a good state, so it can reset failure backoffs\n this.syncState.markAuthCompletion();\n }\n this.authState = newAuth;\n }\n\n private decodeToken(token: string) {\n try {\n return jwtDecode(token);\n } catch (e) {\n this._logVerbose(\n `Error decoding token: ${e instanceof Error ? e.message : \"Unknown error\"}`,\n );\n return null;\n }\n }\n\n private _logVerbose(message: string) {\n this.logger.logVerbose(`${message} [v${this.configVersion}]`);\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Logger } from \"../logging.js\";\nimport { LocalSyncState } from \"./local_state.js\";\nimport { AuthError, IdentityVersion, Transition } from \"./protocol.js\";\nimport { jwtDecode } from \"../../vendor/jwt-decode/index.js\";\n\n// setTimout uses 32 bit integer, so it can only\n// schedule about 24 days in the future.\nconst MAXIMUM_REFRESH_DELAY = 20 * 24 * 60 * 60 * 1000; // 20 days\n\nconst MAX_TOKEN_CONFIRMATION_ATTEMPTS = 2;\n\n/**\n * An async function returning a JWT. Depending on the auth providers\n * configured in convex/auth.config.ts, this may be a JWT-encoded OpenID\n * Connect Identity Token or a traditional JWT.\n *\n * `forceRefreshToken` is `true` if the server rejected a previously\n * returned token or the token is anticipated to expiring soon\n * based on its `exp` time.\n *\n * See {@link ConvexReactClient.setAuth}.\n *\n * @public\n */\nexport type AuthTokenFetcher = (args: {\n forceRefreshToken: boolean;\n}) => Promise<string | null | undefined>;\n\n/**\n * What is provided to the client.\n */\ntype AuthConfig = {\n fetchToken: AuthTokenFetcher;\n onAuthChange: (isAuthenticated: boolean) => void;\n};\n\n/**\n * In general we take 3 steps:\n * 1. Fetch a possibly cached token\n * 2. Immediately fetch a fresh token without using a cache\n * 3. Repeat step 2 before the end of the fresh token's lifetime\n *\n * When we fetch without using a cache we know when the token\n * will expire, and can schedule refetching it.\n *\n * If we get an error before a scheduled refetch, we go back\n * to step 2.\n */\ntype AuthState =\n | { state: \"noAuth\" }\n | {\n state: \"waitingForServerConfirmationOfCachedToken\";\n config: AuthConfig;\n hasRetried: boolean;\n }\n | {\n state: \"initialRefetch\";\n config: AuthConfig;\n }\n | {\n state: \"waitingForServerConfirmationOfFreshToken\";\n config: AuthConfig;\n hadAuth: boolean;\n token: string;\n }\n | {\n state: \"waitingForScheduledRefetch\";\n config: AuthConfig;\n refetchTokenTimeoutId: ReturnType<typeof setTimeout>;\n }\n // Special/weird state when we got a valid token\n // but could not fetch a new one.\n | {\n state: \"notRefetching\";\n config: AuthConfig;\n };\n\n/**\n * Handles the state transitions for auth. The server is the source\n * of truth.\n */\nexport class AuthenticationManager {\n private authState: AuthState = { state: \"noAuth\" };\n // Used to detect races involving `setConfig` calls\n // while a token is being fetched.\n private configVersion = 0;\n // Shared by the BaseClient so that the auth manager can easily inspect it\n private readonly syncState: LocalSyncState;\n // Passed down by BaseClient, sends a message to the server\n private readonly authenticate: (token: string) => IdentityVersion;\n private readonly stopSocket: () => Promise<void>;\n private readonly tryRestartSocket: () => void;\n private readonly pauseSocket: () => void;\n private readonly resumeSocket: () => void;\n // Passed down by BaseClient, sends a message to the server\n private readonly clearAuth: () => void;\n private readonly logger: Logger;\n private readonly refreshTokenLeewaySeconds: number;\n // Number of times we have attempted to confirm the latest token. We retry up\n // to `MAX_TOKEN_CONFIRMATION_ATTEMPTS` times.\n private tokenConfirmationAttempts = 0;\n constructor(\n syncState: LocalSyncState,\n callbacks: {\n authenticate: (token: string) => IdentityVersion;\n stopSocket: () => Promise<void>;\n tryRestartSocket: () => void;\n pauseSocket: () => void;\n resumeSocket: () => void;\n clearAuth: () => void;\n },\n config: {\n refreshTokenLeewaySeconds: number;\n logger: Logger;\n },\n ) {\n this.syncState = syncState;\n this.authenticate = callbacks.authenticate;\n this.stopSocket = callbacks.stopSocket;\n this.tryRestartSocket = callbacks.tryRestartSocket;\n this.pauseSocket = callbacks.pauseSocket;\n this.resumeSocket = callbacks.resumeSocket;\n this.clearAuth = callbacks.clearAuth;\n this.logger = config.logger;\n this.refreshTokenLeewaySeconds = config.refreshTokenLeewaySeconds;\n }\n\n async setConfig(\n fetchToken: AuthTokenFetcher,\n onChange: (isAuthenticated: boolean) => void,\n ) {\n this.resetAuthState();\n this._logVerbose(\"pausing WS for auth token fetch\");\n this.pauseSocket();\n const token = await this.fetchTokenAndGuardAgainstRace(fetchToken, {\n forceRefreshToken: false,\n });\n if (token.isFromOutdatedConfig) {\n return;\n }\n if (token.value) {\n this.setAuthState({\n state: \"waitingForServerConfirmationOfCachedToken\",\n config: { fetchToken, onAuthChange: onChange },\n hasRetried: false,\n });\n this.authenticate(token.value);\n } else {\n this.setAuthState({\n state: \"initialRefetch\",\n config: { fetchToken, onAuthChange: onChange },\n });\n // Try again with `forceRefreshToken: true`\n await this.refetchToken();\n }\n this._logVerbose(\"resuming WS after auth token fetch\");\n this.resumeSocket();\n }\n\n onTransition(serverMessage: Transition) {\n if (\n !this.syncState.isCurrentOrNewerAuthVersion(\n serverMessage.endVersion.identity,\n )\n ) {\n // This is a stale transition - client has moved on to\n // a newer auth version.\n return;\n }\n if (\n serverMessage.endVersion.identity <= serverMessage.startVersion.identity\n ) {\n // This transition did not change auth - it is not a response to Authenticate.\n return;\n }\n\n this._logVerbose(\n `auth state is ${this.authState.state} when handling transition`,\n );\n\n // This transition advanced the auth version, which means the token used was valid\n // and the client and server auth states are in sync.\n this.syncState.markAuthCompletion();\n\n if (this.authState.state === \"waitingForServerConfirmationOfCachedToken\") {\n this._logVerbose(\"server confirmed auth token is valid\");\n void this.refetchToken();\n this.authState.config.onAuthChange(true);\n return;\n }\n if (this.authState.state === \"waitingForServerConfirmationOfFreshToken\") {\n this._logVerbose(\"server confirmed new auth token is valid\");\n this.scheduleTokenRefetch(this.authState.token);\n this.tokenConfirmationAttempts = 0;\n if (!this.authState.hadAuth) {\n this.authState.config.onAuthChange(true);\n }\n }\n }\n\n onAuthError(serverMessage: AuthError) {\n // If the AuthError is not due to updating the token, and we're currently\n // waiting on the result of a token update, ignore.\n if (\n serverMessage.authUpdateAttempted === false &&\n (this.authState.state === \"waitingForServerConfirmationOfFreshToken\" ||\n this.authState.state === \"waitingForServerConfirmationOfCachedToken\")\n ) {\n this._logVerbose(\"ignoring non-auth token expired error\");\n return;\n }\n const { baseVersion } = serverMessage;\n // Versioned AuthErrors are ignored if the client advanced to\n // a newer auth identity\n // Error are reporting the previous version, since the server\n // didn't advance, hence `+ 1`.\n if (!this.syncState.isCurrentOrNewerAuthVersion(baseVersion + 1)) {\n this._logVerbose(\"ignoring auth error for previous auth attempt\");\n return;\n }\n void this.tryToReauthenticate(serverMessage);\n return;\n }\n\n // This is similar to `refetchToken` defined below, in fact we\n // don't represent them as different states, but it is different\n // in that we pause the WebSocket so that mutations\n // don't retry with bad auth.\n private async tryToReauthenticate(serverMessage: AuthError) {\n this._logVerbose(`attempting to reauthenticate: ${serverMessage.error}`);\n if (\n // No way to fetch another token, kaboom\n this.authState.state === \"noAuth\" ||\n // We failed on a fresh token. After a small number of retries, we give up\n // and clear the auth state to avoid infinite retries.\n (this.authState.state === \"waitingForServerConfirmationOfFreshToken\" &&\n this.tokenConfirmationAttempts >= MAX_TOKEN_CONFIRMATION_ATTEMPTS)\n ) {\n this.logger.error(\n `Failed to authenticate: \"${serverMessage.error}\", check your server auth config`,\n );\n if (this.syncState.hasAuth()) {\n this.syncState.clearAuth();\n }\n if (this.authState.state !== \"noAuth\") {\n this.setAndReportAuthFailed(this.authState.config.onAuthChange);\n }\n return;\n }\n if (this.authState.state === \"waitingForServerConfirmationOfFreshToken\") {\n this.tokenConfirmationAttempts++;\n this._logVerbose(\n `retrying reauthentication, ${MAX_TOKEN_CONFIRMATION_ATTEMPTS - this.tokenConfirmationAttempts} attempts remaining`,\n );\n }\n\n await this.stopSocket();\n const token = await this.fetchTokenAndGuardAgainstRace(\n this.authState.config.fetchToken,\n {\n forceRefreshToken: true,\n },\n );\n if (token.isFromOutdatedConfig) {\n return;\n }\n\n if (token.value && this.syncState.isNewAuth(token.value)) {\n this.authenticate(token.value);\n this.setAuthState({\n state: \"waitingForServerConfirmationOfFreshToken\",\n config: this.authState.config,\n token: token.value,\n hadAuth:\n this.authState.state === \"notRefetching\" ||\n this.authState.state === \"waitingForScheduledRefetch\",\n });\n } else {\n this._logVerbose(\"reauthentication failed, could not fetch a new token\");\n if (this.syncState.hasAuth()) {\n this.syncState.clearAuth();\n }\n this.setAndReportAuthFailed(this.authState.config.onAuthChange);\n }\n this.tryRestartSocket();\n }\n\n // Force refetch the token and schedule another refetch\n // before the token expires - an active client should never\n // need to reauthenticate.\n private async refetchToken() {\n if (this.authState.state === \"noAuth\") {\n return;\n }\n this._logVerbose(\"refetching auth token\");\n const token = await this.fetchTokenAndGuardAgainstRace(\n this.authState.config.fetchToken,\n {\n forceRefreshToken: true,\n },\n );\n if (token.isFromOutdatedConfig) {\n return;\n }\n\n if (token.value) {\n if (this.syncState.isNewAuth(token.value)) {\n this.setAuthState({\n state: \"waitingForServerConfirmationOfFreshToken\",\n hadAuth: this.syncState.hasAuth(),\n token: token.value,\n config: this.authState.config,\n });\n this.authenticate(token.value);\n } else {\n this.setAuthState({\n state: \"notRefetching\",\n config: this.authState.config,\n });\n }\n } else {\n this._logVerbose(\"refetching token failed\");\n if (this.syncState.hasAuth()) {\n this.clearAuth();\n }\n this.setAndReportAuthFailed(this.authState.config.onAuthChange);\n }\n // Restart in case this refetch was triggered via schedule during\n // a reauthentication attempt.\n this._logVerbose(\n \"restarting WS after auth token fetch (if currently stopped)\",\n );\n this.tryRestartSocket();\n }\n\n private scheduleTokenRefetch(token: string) {\n if (this.authState.state === \"noAuth\") {\n return;\n }\n const decodedToken = this.decodeToken(token);\n if (!decodedToken) {\n // This is no longer really possible, because\n // we wait on server response before scheduling token refetch,\n // and the server currently requires JWT tokens.\n this.logger.error(\n \"Auth token is not a valid JWT, cannot refetch the token\",\n );\n return;\n }\n // iat: issued at time, UTC seconds timestamp at which the JWT was issued\n // exp: expiration time, UTC seconds timestamp at which the JWT will expire\n const { iat, exp } = decodedToken as { iat?: number; exp?: number };\n if (!iat || !exp) {\n this.logger.error(\n \"Auth token does not have required fields, cannot refetch the token\",\n );\n return;\n }\n // Because the client and server clocks may be out of sync,\n // we only know that the token will expire after `exp - iat`,\n // and since we just fetched a fresh one we know when that\n // will happen.\n const tokenValiditySeconds = exp - iat;\n if (tokenValiditySeconds <= 2) {\n this.logger.error(\n \"Auth token does not live long enough, cannot refetch the token\",\n );\n return;\n }\n // Attempt to refresh the token `refreshTokenLeewaySeconds` before it expires,\n // or immediately if the token is already expiring soon.\n let delay = Math.min(\n MAXIMUM_REFRESH_DELAY,\n (tokenValiditySeconds - this.refreshTokenLeewaySeconds) * 1000,\n );\n if (delay <= 0) {\n // Refetch immediately, but this might be due to configuring a `refreshTokenLeewaySeconds`\n // that is too large compared to the token's actual lifetime.\n this.logger.warn(\n `Refetching auth token immediately, configured leeway ${this.refreshTokenLeewaySeconds}s is larger than the token's lifetime ${tokenValiditySeconds}s`,\n );\n delay = 0;\n }\n const refetchTokenTimeoutId = setTimeout(() => {\n this._logVerbose(\"running scheduled token refetch\");\n void this.refetchToken();\n }, delay);\n this.setAuthState({\n state: \"waitingForScheduledRefetch\",\n refetchTokenTimeoutId,\n config: this.authState.config,\n });\n this._logVerbose(\n `scheduled preemptive auth token refetching in ${delay}ms`,\n );\n }\n\n // Protects against simultaneous calls to `setConfig`\n // while we're fetching a token\n private async fetchTokenAndGuardAgainstRace(\n fetchToken: AuthTokenFetcher,\n fetchArgs: {\n forceRefreshToken: boolean;\n },\n ) {\n const originalConfigVersion = ++this.configVersion;\n this._logVerbose(\n `fetching token with config version ${originalConfigVersion}`,\n );\n const token = await fetchToken(fetchArgs);\n if (this.configVersion !== originalConfigVersion) {\n // This is a stale config\n this._logVerbose(\n `stale config version, expected ${originalConfigVersion}, got ${this.configVersion}`,\n );\n return { isFromOutdatedConfig: true };\n }\n return { isFromOutdatedConfig: false, value: token };\n }\n\n stop() {\n this.resetAuthState();\n // Bump this in case we are mid-token-fetch when we get stopped\n this.configVersion++;\n this._logVerbose(`config version bumped to ${this.configVersion}`);\n }\n\n private setAndReportAuthFailed(\n onAuthChange: (authenticated: boolean) => void,\n ) {\n onAuthChange(false);\n this.resetAuthState();\n }\n\n private resetAuthState() {\n this.setAuthState({ state: \"noAuth\" });\n }\n\n private setAuthState(newAuth: AuthState) {\n const authStateForLog =\n newAuth.state === \"waitingForServerConfirmationOfFreshToken\"\n ? {\n hadAuth: newAuth.hadAuth,\n state: newAuth.state,\n token: `...${newAuth.token.slice(-7)}`,\n }\n : { state: newAuth.state };\n this._logVerbose(\n `setting auth state to ${JSON.stringify(authStateForLog)}`,\n );\n switch (newAuth.state) {\n case \"waitingForScheduledRefetch\":\n case \"notRefetching\":\n case \"noAuth\":\n this.tokenConfirmationAttempts = 0;\n break;\n case \"waitingForServerConfirmationOfFreshToken\":\n case \"waitingForServerConfirmationOfCachedToken\":\n case \"initialRefetch\":\n break;\n default: {\n newAuth satisfies never;\n }\n }\n if (this.authState.state === \"waitingForScheduledRefetch\") {\n // TODO: this side-effect would be better situated with scheduling refetch\n clearTimeout(this.authState.refetchTokenTimeoutId);\n }\n this.authState = newAuth;\n }\n\n private decodeToken(token: string) {\n try {\n return jwtDecode(token);\n } catch (e) {\n this._logVerbose(\n `Error decoding token: ${e instanceof Error ? e.message : \"Unknown error\"}`,\n );\n return null;\n }\n }\n\n private _logVerbose(message: string) {\n this.logger.logVerbose(`${message} [v${this.configVersion}]`);\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -38,12 +38,6 @@ const serverDisconnectErrors = {
38
38
  VectorIndexesUnavailable: { timeout: 1e3 },
39
39
  SearchIndexesUnavailable: { timeout: 1e3 },
40
40
  TableSummariesUnavailable: { timeout: 1e3 },
41
- // ErrorMetadata::service_unavailable() when backend/conductor is unreachable
42
- ServiceUnavailable: { timeout: 3e3 },
43
- // ErrorMetadata::rejected_before_execution() when funrun workers are unavailable
44
- WorkerOverloaded: { timeout: 3e3 },
45
- IsolateNotClean: { timeout: 3e3 },
46
- InitialPermitTimeoutError: { timeout: 3e3 },
47
41
  // More ErrorMetadata::overloaded()
48
42
  VectorIndexTooLarge: { timeout: 3e3 },
49
43
  SearchIndexTooLarge: { timeout: 3e3 },
@@ -97,7 +91,7 @@ export class WebSocketManager {
97
91
  this.connectionCount = 0;
98
92
  this.lastCloseReason = "InitialConnect";
99
93
  this.defaultInitialBackoff = 1e3;
100
- this.maxBackoff = 64e3;
94
+ this.maxBackoff = 16e3;
101
95
  this.retries = 0;
102
96
  this.serverInactivityThreshold = 6e4;
103
97
  this.reconnectDueToServerInactivityTimeout = null;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../src/browser/sync/web_socket_manager.ts"],
4
- "sourcesContent": ["import { Logger } from \"../logging.js\";\nimport {\n ClientMessage,\n encodeClientMessage,\n parseServerMessage,\n ServerMessage,\n Transition,\n TransitionChunk,\n} from \"./protocol.js\";\n\nconst CLOSE_NORMAL = 1000;\nconst CLOSE_GOING_AWAY = 1001;\nconst CLOSE_NO_STATUS = 1005;\n/** Convex-specific close code representing a \"404 Not Found\".\n * The edge Onramp accepts websocket upgrades before confirming that the\n * intended destination exists, so this code is sent once we've discovered that\n * the destination does not exist.\n */\nconst CLOSE_NOT_FOUND = 4040;\n\n/**\n * The various states our WebSocket can be in:\n *\n * - \"disconnected\": We don't have a WebSocket, but plan to create one.\n * - \"connecting\": We have created the WebSocket and are waiting for the\n * `onOpen` callback.\n * - \"ready\": We have an open WebSocket.\n * - \"stopped\": The WebSocket was closed and a new one can be created via `.restart()`.\n * - \"terminated\": We have closed the WebSocket and will never create a new one.\n *\n *\n * WebSocket State Machine\n * -----------------------\n * initialState: disconnected\n * validTransitions:\n * disconnected:\n * new WebSocket() -> connecting\n * terminate() -> terminated\n * connecting:\n * onopen -> ready\n * close() -> disconnected\n * terminate() -> terminated\n * ready:\n * close() -> disconnected\n * stop() -> stopped\n * terminate() -> terminated\n * stopped:\n * restart() -> connecting\n * terminate() -> terminated\n * terminalStates:\n * terminated\n *\n *\n *\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u250C\u2500\u2500\u2500\u2500terminate()\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502 disconnected \u2502\u25C0\u2500\u2510\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n * \u25BC \u2502 \u25B2 \u2502\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 new WebSocket() \u2502 \u2502\n * \u250C\u2500\u25B6\u2502 terminated \u2502\u25C0\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502 \u2502\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502 \u2502 \u2502\n * \u2502 \u25B2 terminate() \u2502 close() close()\n * \u2502 terminate() \u2502 \u2502 \u2502 \u2502\n * \u2502 \u2502 \u2502 \u25BC \u2502 \u2502\n * \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n * \u2502 \u2502 stopped \u2502\u2500\u2500restart()\u2500\u2500\u2500\u25B6\u2502 connecting \u2502 \u2502\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n * \u2502 \u25B2 \u2502 \u2502\n * \u2502 \u2502 onopen \u2502\n * \u2502 \u2502 \u2502 \u2502\n * \u2502 \u2502 \u25BC \u2502\n * terminate() \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500stop()\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502 ready \u2502\u2500\u2500\u2518\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * \u2502 \u2502\n * \u2502 \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n *\n * The `connecting` and `ready` state have a sub-state-machine for pausing.\n */\n\ntype Socket =\n | { state: \"disconnected\" }\n | { state: \"connecting\"; ws: WebSocket; paused: \"yes\" | \"no\" }\n | { state: \"ready\"; ws: WebSocket; paused: \"yes\" | \"no\" | \"uninitialized\" }\n | { state: \"stopped\" }\n | { state: \"terminated\" };\n\nexport type ReconnectMetadata = {\n connectionCount: number;\n lastCloseReason: string | null;\n clientTs: number;\n};\n\nexport type OnMessageResponse = {\n hasSyncedPastLastReconnect: boolean;\n};\n\nlet firstTime: number | undefined;\nfunction monotonicMillis() {\n if (firstTime === undefined) {\n firstTime = Date.now();\n }\n if (typeof performance === \"undefined\" || !performance.now) {\n return Date.now();\n }\n return Math.round(firstTime + performance.now());\n}\n\nfunction prettyNow() {\n return `t=${Math.round((monotonicMillis() - firstTime!) / 100) / 10}s`;\n}\n\nconst serverDisconnectErrors = {\n // A known error, e.g. during a restart or push\n InternalServerError: { timeout: 1000 },\n // ErrorMetadata::overloaded() messages that we realy should back off\n SubscriptionsWorkerFullError: { timeout: 3000 },\n TooManyConcurrentRequests: { timeout: 3000 },\n CommitterFullError: { timeout: 3000 },\n AwsTooManyRequestsException: { timeout: 3000 },\n ExecuteFullError: { timeout: 3000 },\n SystemTimeoutError: { timeout: 3000 },\n ExpiredInQueue: { timeout: 3000 },\n // ErrorMetadata::feature_temporarily_unavailable() that typically indicate a deploy just happened\n VectorIndexesUnavailable: { timeout: 1000 },\n SearchIndexesUnavailable: { timeout: 1000 },\n TableSummariesUnavailable: { timeout: 1000 },\n // ErrorMetadata::service_unavailable() when backend/conductor is unreachable\n ServiceUnavailable: { timeout: 3000 },\n // ErrorMetadata::rejected_before_execution() when funrun workers are unavailable\n WorkerOverloaded: { timeout: 3000 },\n IsolateNotClean: { timeout: 3000 },\n InitialPermitTimeoutError: { timeout: 3000 },\n // More ErrorMetadata::overloaded()\n VectorIndexTooLarge: { timeout: 3000 },\n SearchIndexTooLarge: { timeout: 3000 },\n TooManyWritesInTimePeriod: { timeout: 3000 },\n} as const satisfies Record<string, { timeout: number }>;\n\ntype ServerDisconnectError = keyof typeof serverDisconnectErrors | \"Unknown\";\n\nfunction classifyDisconnectError(s?: string): ServerDisconnectError {\n if (s === undefined) return \"Unknown\";\n // startsWith so more info could be at the end (although currently there isn't)\n\n for (const prefix of Object.keys(\n serverDisconnectErrors,\n ) as ServerDisconnectError[]) {\n if (s.startsWith(prefix)) {\n return prefix;\n }\n }\n return \"Unknown\";\n}\n\n/**\n * A wrapper around a websocket that handles errors, reconnection, and message\n * parsing.\n */\nexport class WebSocketManager {\n private socket: Socket;\n\n private connectionCount: number;\n private _hasEverConnected: boolean = false;\n private lastCloseReason:\n | \"InitialConnect\"\n | \"OnCloseInvoked\"\n | (string & {}) // a full serverErrorReason (not just the prefix) or a new one\n | null;\n\n // State for assembling the split-up Transition currently being received.\n private transitionChunkBuffer: {\n chunks: string[];\n totalParts: number;\n transitionId: string;\n } | null = null;\n\n /** Upon HTTPS/WSS failure, the first jittered backoff duration, in ms. */\n private readonly defaultInitialBackoff: number;\n\n /** We backoff exponentially, but we need to cap that--this is the jittered max. */\n private readonly maxBackoff: number;\n\n /** How many times have we failed consecutively? */\n private retries: number;\n\n /** How long before lack of server response causes us to initiate a reconnect,\n * in ms */\n private readonly serverInactivityThreshold: number;\n\n private reconnectDueToServerInactivityTimeout: ReturnType<\n typeof setTimeout\n > | null;\n\n /** Scheduled reconnect state: timeout handle and timing info */\n private scheduledReconnect: {\n timeout: ReturnType<typeof setTimeout>;\n scheduledAt: number;\n backoffMs: number;\n } | null = null;\n\n private networkOnlineHandler: (() => void) | null = null;\n\n /** Pending event to send after reconnecting due to network recovery */\n private pendingNetworkRecoveryInfo: { timeSavedMs: number } | null = null;\n\n private readonly uri: string;\n private readonly onOpen: (reconnectMetadata: ReconnectMetadata) => void;\n private readonly onResume: () => void;\n private readonly onMessage: (message: ServerMessage) => OnMessageResponse;\n private readonly webSocketConstructor: typeof WebSocket;\n private readonly logger: Logger;\n private readonly onServerDisconnectError:\n | ((message: string) => void)\n | undefined;\n\n constructor(\n uri: string,\n callbacks: {\n onOpen: (reconnectMetadata: ReconnectMetadata) => void;\n onResume: () => void;\n onMessage: (message: ServerMessage) => OnMessageResponse;\n onServerDisconnectError?: ((message: string) => void) | undefined;\n },\n webSocketConstructor: typeof WebSocket,\n logger: Logger,\n private readonly markConnectionStateDirty: () => void,\n private readonly debug: boolean,\n ) {\n this.webSocketConstructor = webSocketConstructor;\n this.socket = { state: \"disconnected\" };\n this.connectionCount = 0;\n this.lastCloseReason = \"InitialConnect\";\n\n // backoff for unknown errors\n this.defaultInitialBackoff = 1000;\n this.maxBackoff = 64000;\n this.retries = 0;\n\n // Ping messages (sync protocol Pings, not WebSocket protocol Pings) are\n // sent every 15s in the absence of other messages. But a single large\n // Transition or other downstream message can hog the line so this\n // threshold is set higher to prevent clients from giving up.\n this.serverInactivityThreshold = 60000;\n this.reconnectDueToServerInactivityTimeout = null;\n\n this.uri = uri;\n this.onOpen = callbacks.onOpen;\n this.onResume = callbacks.onResume;\n this.onMessage = callbacks.onMessage;\n this.onServerDisconnectError = callbacks.onServerDisconnectError;\n this.logger = logger;\n\n // Set up network online event listener\n this.setupNetworkListener();\n\n this.connect();\n }\n\n private setSocketState(state: Socket) {\n this.socket = state;\n this._logVerbose(\n `socket state changed: ${this.socket.state}, paused: ${\n \"paused\" in this.socket ? this.socket.paused : undefined\n }`,\n );\n this.markConnectionStateDirty();\n }\n\n private setupNetworkListener() {\n // Only set up listener if we're in a browser environment with addEventListener\n // (React Native has window but not addEventListener)\n if (\n typeof window === \"undefined\" ||\n typeof window.addEventListener !== \"function\"\n ) {\n return;\n }\n // Avoid registering duplicate listeners\n if (this.networkOnlineHandler !== null) {\n return;\n }\n\n this.networkOnlineHandler = () => {\n this._logVerbose(\"network online event detected\");\n this.tryReconnectImmediately();\n };\n\n window.addEventListener(\"online\", this.networkOnlineHandler);\n this._logVerbose(\"network online event listener registered\");\n }\n\n private cleanupNetworkListener() {\n if (\n this.networkOnlineHandler &&\n typeof window !== \"undefined\" &&\n typeof window.removeEventListener === \"function\"\n ) {\n window.removeEventListener(\"online\", this.networkOnlineHandler);\n this.networkOnlineHandler = null;\n this._logVerbose(\"network online event listener removed\");\n }\n }\n\n private assembleTransition(chunk: TransitionChunk): Transition | null {\n if (\n chunk.partNumber < 0 ||\n chunk.partNumber >= chunk.totalParts ||\n chunk.totalParts === 0 ||\n (this.transitionChunkBuffer &&\n (this.transitionChunkBuffer.totalParts !== chunk.totalParts ||\n this.transitionChunkBuffer.transitionId !== chunk.transitionId))\n ) {\n // Throwing an error doesn't crash the client, so clear the buffer.\n this.transitionChunkBuffer = null;\n throw new Error(\"Invalid TransitionChunk\");\n }\n\n if (this.transitionChunkBuffer === null) {\n this.transitionChunkBuffer = {\n chunks: [],\n totalParts: chunk.totalParts,\n transitionId: chunk.transitionId,\n };\n }\n\n if (chunk.partNumber !== this.transitionChunkBuffer.chunks.length) {\n // Throwing an error doesn't crash the client, so clear the buffer.\n const expectedLength = this.transitionChunkBuffer.chunks.length;\n this.transitionChunkBuffer = null;\n throw new Error(\n `TransitionChunk received out of order: expected part ${expectedLength}, got ${chunk.partNumber}`,\n );\n }\n\n this.transitionChunkBuffer.chunks.push(chunk.chunk);\n\n if (this.transitionChunkBuffer.chunks.length === chunk.totalParts) {\n const fullJson = this.transitionChunkBuffer.chunks.join(\"\");\n this.transitionChunkBuffer = null;\n\n const transition = parseServerMessage(JSON.parse(fullJson));\n if (transition.type !== \"Transition\") {\n throw new Error(\n `Expected Transition, got ${transition.type} after assembling chunks`,\n );\n }\n return transition;\n }\n\n return null;\n }\n\n private connect() {\n if (this.socket.state === \"terminated\") {\n return;\n }\n if (\n this.socket.state !== \"disconnected\" &&\n this.socket.state !== \"stopped\"\n ) {\n throw new Error(\n \"Didn't start connection from disconnected state: \" + this.socket.state,\n );\n }\n\n const ws = new this.webSocketConstructor(this.uri);\n this._logVerbose(\"constructed WebSocket\");\n this.setSocketState({\n state: \"connecting\",\n ws,\n paused: \"no\",\n });\n\n // Kick off server inactivity timer before WebSocket connection is established\n // so we can detect cases where handshake fails.\n // The `onopen` event only fires after the connection is established:\n // Source: https://datatracker.ietf.org/doc/html/rfc6455#page-19:~:text=_The%20WebSocket%20Connection%20is%20Established_,-and\n this.resetServerInactivityTimeout();\n\n ws.onopen = () => {\n this.logger.logVerbose(\"begin ws.onopen\");\n if (this.socket.state !== \"connecting\") {\n throw new Error(\"onopen called with socket not in connecting state\");\n }\n this.setSocketState({\n state: \"ready\",\n ws,\n paused: this.socket.paused === \"yes\" ? \"uninitialized\" : \"no\",\n });\n this.resetServerInactivityTimeout();\n if (this.socket.paused === \"no\") {\n this._hasEverConnected = true;\n this.onOpen({\n connectionCount: this.connectionCount,\n lastCloseReason: this.lastCloseReason,\n clientTs: monotonicMillis(),\n });\n }\n\n if (this.lastCloseReason !== \"InitialConnect\") {\n if (this.lastCloseReason) {\n this.logger.log(\n \"WebSocket reconnected at\",\n prettyNow(),\n \"after disconnect due to\",\n this.lastCloseReason,\n );\n } else {\n this.logger.log(\"WebSocket reconnected at\", prettyNow());\n }\n }\n\n this.connectionCount += 1;\n this.lastCloseReason = null;\n\n // Send event for network recovery reconnect if applicable\n if (this.pendingNetworkRecoveryInfo !== null) {\n const { timeSavedMs } = this.pendingNetworkRecoveryInfo;\n this.pendingNetworkRecoveryInfo = null;\n this.sendMessage({\n type: \"Event\",\n eventType: \"NetworkRecoveryReconnect\",\n event: { timeSavedMs },\n });\n this.logger.log(\n `Network recovery reconnect saved ~${Math.round(timeSavedMs / 1000)}s of waiting`,\n );\n }\n };\n // NB: The WebSocket API calls `onclose` even if connection fails, so we can route all error paths through `onclose`.\n ws.onerror = (error) => {\n this.transitionChunkBuffer = null;\n const message = (error as ErrorEvent).message;\n if (message) {\n this.logger.log(`WebSocket error message: ${message}`);\n }\n };\n ws.onmessage = (message) => {\n this.resetServerInactivityTimeout();\n const messageLength = message.data.length;\n let serverMessage = parseServerMessage(JSON.parse(message.data));\n this._logVerbose(`received ws message with type ${serverMessage.type}`);\n\n // Ping's only purpose is to reset the server inactivity timer.\n if (serverMessage.type === \"Ping\") {\n return;\n }\n\n // TransitionChunks never reach the main client logic.\n if (serverMessage.type === \"TransitionChunk\") {\n const transition = this.assembleTransition(serverMessage);\n if (!transition) {\n return;\n }\n serverMessage = transition;\n this._logVerbose(\n `assembled full ws message of type ${serverMessage.type}`,\n );\n }\n\n if (this.transitionChunkBuffer !== null) {\n this.transitionChunkBuffer = null;\n this.logger.log(\n `Received unexpected ${serverMessage.type} while buffering TransitionChunks`,\n );\n }\n\n if (serverMessage.type === \"Transition\") {\n this.reportLargeTransition({\n messageLength,\n transition: serverMessage,\n });\n }\n const response = this.onMessage(serverMessage);\n if (response.hasSyncedPastLastReconnect) {\n // Reset backoff to 0 once all outstanding requests are complete.\n this.retries = 0;\n this.markConnectionStateDirty();\n }\n };\n ws.onclose = (event) => {\n this._logVerbose(\"begin ws.onclose\");\n this.transitionChunkBuffer = null;\n if (this.lastCloseReason === null) {\n // event.reason is often an empty string\n this.lastCloseReason = event.reason || `closed with code ${event.code}`;\n }\n if (\n event.code !== CLOSE_NORMAL &&\n event.code !== CLOSE_GOING_AWAY && // This commonly gets fired on mobile apps when the app is backgrounded\n event.code !== CLOSE_NO_STATUS &&\n event.code !== CLOSE_NOT_FOUND // Note that we want to retry on a 404, as it can be transient during a push.\n ) {\n let msg = `WebSocket closed with code ${event.code}`;\n if (event.reason) {\n msg += `: ${event.reason}`;\n }\n this.logger.log(msg);\n if (this.onServerDisconnectError && event.reason) {\n // This callback is a unstable API, InternalServerErrors in particular may be removed\n // since they reflect expected temporary downtime. But until a quantitative measure\n // of uptime is reported this unstable API errs on the inclusive side.\n this.onServerDisconnectError(msg);\n }\n }\n const reason = classifyDisconnectError(event.reason);\n this.scheduleReconnect(reason);\n return;\n };\n }\n\n /**\n * @returns The state of the {@link Socket}.\n */\n socketState(): string {\n return this.socket.state;\n }\n\n /**\n * @param message - A ClientMessage to send.\n * @returns Whether the message (might have been) sent.\n */\n sendMessage(message: ClientMessage) {\n const messageForLog = {\n type: message.type,\n ...(message.type === \"Authenticate\" && message.tokenType === \"User\"\n ? {\n value: `...${message.value.slice(-7)}`,\n }\n : {}),\n };\n if (this.socket.state === \"ready\" && this.socket.paused === \"no\") {\n const encodedMessage = encodeClientMessage(message);\n const request = JSON.stringify(encodedMessage);\n let sent = false;\n try {\n this.socket.ws.send(request);\n sent = true;\n } catch (error: any) {\n this.logger.log(\n `Failed to send message on WebSocket, reconnecting: ${error}`,\n );\n this.closeAndReconnect(\"FailedToSendMessage\");\n }\n this._logVerbose(\n `${sent ? \"sent\" : \"failed to send\"} message with type ${message.type}: ${JSON.stringify(\n messageForLog,\n )}`,\n );\n return true;\n }\n this._logVerbose(\n `message not sent (socket state: ${this.socket.state}, paused: ${\"paused\" in this.socket ? this.socket.paused : undefined}): ${JSON.stringify(\n messageForLog,\n )}`,\n );\n\n return false;\n }\n\n private resetServerInactivityTimeout() {\n if (this.socket.state === \"terminated\") {\n // Don't reset any timers if we were trying to terminate.\n return;\n }\n if (this.reconnectDueToServerInactivityTimeout !== null) {\n clearTimeout(this.reconnectDueToServerInactivityTimeout);\n this.reconnectDueToServerInactivityTimeout = null;\n }\n this.reconnectDueToServerInactivityTimeout = setTimeout(() => {\n this.closeAndReconnect(\"InactiveServer\");\n }, this.serverInactivityThreshold);\n }\n\n private scheduleReconnect(reason: \"client\" | ServerDisconnectError) {\n // Cancel any existing scheduled reconnect to avoid multiple reconnects\n if (this.scheduledReconnect) {\n clearTimeout(this.scheduledReconnect.timeout);\n this.scheduledReconnect = null;\n }\n\n this.socket = { state: \"disconnected\" };\n const backoff = this.nextBackoff(reason);\n this.markConnectionStateDirty();\n this.logger.log(`Attempting reconnect in ${Math.round(backoff)}ms`);\n\n const scheduledAt = monotonicMillis();\n const timeoutId = setTimeout(() => {\n // Only proceed if this timeout hasn't been cleared\n if (this.scheduledReconnect?.timeout === timeoutId) {\n this.scheduledReconnect = null;\n this.connect();\n }\n }, backoff);\n\n this.scheduledReconnect = {\n timeout: timeoutId,\n scheduledAt,\n backoffMs: backoff,\n };\n }\n\n /**\n * Close the WebSocket and schedule a reconnect.\n *\n * This should be used when we hit an error and would like to restart the session.\n */\n private closeAndReconnect(closeReason: string) {\n this._logVerbose(`begin closeAndReconnect with reason ${closeReason}`);\n switch (this.socket.state) {\n case \"disconnected\":\n case \"terminated\":\n case \"stopped\":\n // Nothing to do if we don't have a WebSocket.\n return;\n case \"connecting\":\n case \"ready\": {\n this.lastCloseReason = closeReason;\n // Close the old socket asynchronously, we'll open a new socket in reconnect.\n void this.close();\n this.scheduleReconnect(\"client\");\n return;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n this.socket satisfies never;\n }\n }\n }\n\n /**\n * Close the WebSocket, being careful to clear the onclose handler to avoid re-entrant\n * calls. Use this instead of directly calling `ws.close()`\n *\n * It is the callers responsibility to update the state after this method is called so that the\n * closed socket is not accessible or used again after this method is called\n */\n private close(): Promise<void> {\n this.transitionChunkBuffer = null;\n switch (this.socket.state) {\n case \"disconnected\":\n case \"terminated\":\n case \"stopped\":\n // Nothing to do if we don't have a WebSocket.\n return Promise.resolve();\n case \"connecting\": {\n const ws = this.socket.ws;\n // Messages can still be received after close but we're not interested.\n ws.onmessage = (_message) => {\n this._logVerbose(\"Ignoring message received after close\");\n };\n return new Promise((r) => {\n ws.onclose = () => {\n this._logVerbose(\"Closed after connecting\");\n r();\n };\n ws.onopen = () => {\n this._logVerbose(\"Opened after connecting\");\n ws.close();\n };\n });\n }\n case \"ready\": {\n this._logVerbose(\"ws.close called\");\n const ws = this.socket.ws;\n // Messages can still be received after close but we're not interested.\n ws.onmessage = (_message) => {\n this._logVerbose(\"Ignoring message received after close\");\n };\n const result: Promise<void> = new Promise((r) => {\n ws.onclose = () => {\n r();\n };\n });\n ws.close();\n return result;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n this.socket satisfies never;\n return Promise.resolve();\n }\n }\n }\n\n /**\n * Close the WebSocket and do not reconnect.\n * @returns A Promise that resolves when the WebSocket `onClose` callback is called.\n */\n terminate(): Promise<void> {\n if (this.reconnectDueToServerInactivityTimeout) {\n clearTimeout(this.reconnectDueToServerInactivityTimeout);\n }\n if (this.scheduledReconnect) {\n clearTimeout(this.scheduledReconnect.timeout);\n this.scheduledReconnect = null;\n }\n this.cleanupNetworkListener();\n switch (this.socket.state) {\n case \"terminated\":\n case \"stopped\":\n case \"disconnected\":\n case \"connecting\":\n case \"ready\": {\n const result = this.close();\n this.setSocketState({ state: \"terminated\" });\n return result;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n this.socket satisfies never;\n throw new Error(\n `Invalid websocket state: ${(this.socket as any).state}`,\n );\n }\n }\n }\n\n stop(): Promise<void> {\n switch (this.socket.state) {\n case \"terminated\":\n // If we're terminating we ignore stop\n return Promise.resolve();\n case \"connecting\":\n case \"stopped\":\n case \"disconnected\":\n case \"ready\": {\n this.cleanupNetworkListener();\n const result = this.close();\n this.socket = { state: \"stopped\" };\n return result;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n this.socket satisfies never;\n return Promise.resolve();\n }\n }\n }\n\n /**\n * Create a new WebSocket after a previous `stop()`, unless `terminate()` was\n * called before.\n */\n tryRestart(): void {\n switch (this.socket.state) {\n case \"stopped\":\n break;\n case \"terminated\":\n case \"connecting\":\n case \"ready\":\n case \"disconnected\":\n this.logger.logVerbose(\"Restart called without stopping first\");\n return;\n default: {\n // Enforce that the switch-case is exhaustive.\n this.socket satisfies never;\n }\n }\n this.setupNetworkListener();\n this.connect();\n }\n\n pause(): void {\n switch (this.socket.state) {\n case \"disconnected\":\n case \"stopped\":\n case \"terminated\":\n // If already stopped or stopping ignore.\n return;\n case \"connecting\":\n case \"ready\": {\n this.socket = { ...this.socket, paused: \"yes\" };\n return;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n this.socket satisfies never;\n return;\n }\n }\n }\n\n /**\n * Try to reconnect immediately, canceling any scheduled reconnect.\n * This is useful when detecting network recovery.\n * Only takes action if we're in disconnected state (waiting to reconnect).\n */\n tryReconnectImmediately(): void {\n this._logVerbose(\"tryReconnectImmediately called\");\n\n // Only reconnect if we're in disconnected state (waiting to reconnect)\n if (this.socket.state !== \"disconnected\") {\n this._logVerbose(\n `tryReconnectImmediately called but socket state is ${this.socket.state}, no action taken`,\n );\n return;\n }\n\n // Track how much time we saved by reconnecting immediately\n let timeSavedMs: number | null = null;\n if (this.scheduledReconnect) {\n const elapsed = monotonicMillis() - this.scheduledReconnect.scheduledAt;\n timeSavedMs = Math.max(0, this.scheduledReconnect.backoffMs - elapsed);\n this._logVerbose(\n `would have waited ${Math.round(timeSavedMs)}ms more (backoff was ${Math.round(this.scheduledReconnect.backoffMs)}ms, elapsed ${Math.round(elapsed)}ms)`,\n );\n // Cancel the scheduled reconnect\n clearTimeout(this.scheduledReconnect.timeout);\n this.scheduledReconnect = null;\n this._logVerbose(\"canceled scheduled reconnect\");\n }\n\n this.logger.log(\"Network recovery detected, reconnecting immediately\");\n // Store the time saved to send as an event after we connect\n this.pendingNetworkRecoveryInfo =\n timeSavedMs !== null ? { timeSavedMs } : null;\n this.connect();\n }\n\n /**\n * Resume the state machine if previously paused.\n */\n resume(): void {\n switch (this.socket.state) {\n case \"connecting\":\n this.socket = { ...this.socket, paused: \"no\" };\n return;\n case \"ready\":\n if (this.socket.paused === \"uninitialized\") {\n this.socket = { ...this.socket, paused: \"no\" };\n this.onOpen({\n connectionCount: this.connectionCount,\n lastCloseReason: this.lastCloseReason,\n clientTs: monotonicMillis(),\n });\n } else if (this.socket.paused === \"yes\") {\n this.socket = { ...this.socket, paused: \"no\" };\n this.onResume();\n }\n return;\n case \"terminated\":\n case \"stopped\":\n case \"disconnected\":\n // Ignore resume if not paused, perhaps we already resumed.\n return;\n default: {\n // Enforce that the switch-case is exhaustive.\n this.socket satisfies never;\n }\n }\n this.connect();\n }\n\n connectionState(): {\n isConnected: boolean;\n hasEverConnected: boolean;\n connectionCount: number;\n connectionRetries: number;\n } {\n return {\n isConnected: this.socket.state === \"ready\",\n hasEverConnected: this._hasEverConnected,\n connectionCount: this.connectionCount,\n connectionRetries: this.retries,\n };\n }\n\n private _logVerbose(message: string) {\n this.logger.logVerbose(message);\n }\n\n private nextBackoff(reason: \"client\" | ServerDisconnectError): number {\n const initialBackoff: number =\n reason === \"client\"\n ? 100 // There's no evidence of a server problem, retry quickly\n : reason === \"Unknown\"\n ? this.defaultInitialBackoff\n : serverDisconnectErrors[reason].timeout;\n\n const baseBackoff = initialBackoff * Math.pow(2, this.retries);\n this.retries += 1;\n const actualBackoff = Math.min(baseBackoff, this.maxBackoff);\n const jitter = actualBackoff * (Math.random() - 0.5);\n return actualBackoff + jitter;\n }\n\n private reportLargeTransition({\n transition,\n messageLength,\n }: {\n transition: Transition;\n messageLength: number;\n }) {\n if (\n transition.clientClockSkew === undefined ||\n transition.serverTs === undefined\n ) {\n return;\n }\n\n const transitionTransitTime =\n monotonicMillis() - // client time now\n // clientClockSkew = (server time + upstream latency) - client time\n // clientClockSkew is \"how many milliseconds behind (slow) is the client clock\"\n // but the latency of the Connect message inflates this, making it appear further behind\n transition.clientClockSkew -\n transition.serverTs / 1_000_000; // server time when transition was sent\n const prettyTransitionTime = `${Math.round(transitionTransitTime)}ms`;\n const prettyMessageMB = `${Math.round(messageLength / 10_000) / 100}MB`;\n const bytesPerSecond = messageLength / (transitionTransitTime / 1000);\n const prettyBytesPerSecond = `${Math.round(bytesPerSecond / 10_000) / 100}MB per second`;\n this._logVerbose(\n `received ${prettyMessageMB} transition in ${prettyTransitionTime} at ${prettyBytesPerSecond}`,\n );\n\n // Warnings that will show up for *all users*, so don't be too aggressive.\n // These can be silenced (along with reconnection messages) by setting `logger: false` in client options.\n if (messageLength > 20_000_000) {\n // Big enough that the developer should be made aware of this.\n this.logger.log(\n `received query results totaling more that 20MB (${prettyMessageMB}) which will take a long time to download on slower connections`,\n );\n } else if (transitionTransitTime > 20_000) {\n // Long enough that a pattern of these should be interesting to a developer, but be aware that\n // weak connections, putting clients to sleep, backgrounding etc. could all cause this too.\n this.logger.log(\n `received query results totaling ${prettyMessageMB} which took more than 20s to arrive (${prettyTransitionTime})`,\n );\n }\n\n if (this.debug) {\n // debug means \"reportDebugInfoToConvex\" is set so this can be aggressive.\n this.sendMessage({\n type: \"Event\",\n eventType: \"ClientReceivedTransition\",\n event: { transitionTransitTime, messageLength },\n });\n }\n }\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { Logger } from \"../logging.js\";\nimport {\n ClientMessage,\n encodeClientMessage,\n parseServerMessage,\n ServerMessage,\n Transition,\n TransitionChunk,\n} from \"./protocol.js\";\n\nconst CLOSE_NORMAL = 1000;\nconst CLOSE_GOING_AWAY = 1001;\nconst CLOSE_NO_STATUS = 1005;\n/** Convex-specific close code representing a \"404 Not Found\".\n * The edge Onramp accepts websocket upgrades before confirming that the\n * intended destination exists, so this code is sent once we've discovered that\n * the destination does not exist.\n */\nconst CLOSE_NOT_FOUND = 4040;\n\n/**\n * The various states our WebSocket can be in:\n *\n * - \"disconnected\": We don't have a WebSocket, but plan to create one.\n * - \"connecting\": We have created the WebSocket and are waiting for the\n * `onOpen` callback.\n * - \"ready\": We have an open WebSocket.\n * - \"stopped\": The WebSocket was closed and a new one can be created via `.restart()`.\n * - \"terminated\": We have closed the WebSocket and will never create a new one.\n *\n *\n * WebSocket State Machine\n * -----------------------\n * initialState: disconnected\n * validTransitions:\n * disconnected:\n * new WebSocket() -> connecting\n * terminate() -> terminated\n * connecting:\n * onopen -> ready\n * close() -> disconnected\n * terminate() -> terminated\n * ready:\n * close() -> disconnected\n * stop() -> stopped\n * terminate() -> terminated\n * stopped:\n * restart() -> connecting\n * terminate() -> terminated\n * terminalStates:\n * terminated\n *\n *\n *\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n * \u250C\u2500\u2500\u2500\u2500terminate()\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502 disconnected \u2502\u25C0\u2500\u2510\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n * \u25BC \u2502 \u25B2 \u2502\n * \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 new WebSocket() \u2502 \u2502\n * \u250C\u2500\u25B6\u2502 terminated \u2502\u25C0\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502 \u2502 \u2502\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502 \u2502 \u2502 \u2502\n * \u2502 \u25B2 terminate() \u2502 close() close()\n * \u2502 terminate() \u2502 \u2502 \u2502 \u2502\n * \u2502 \u2502 \u2502 \u25BC \u2502 \u2502\n * \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n * \u2502 \u2502 stopped \u2502\u2500\u2500restart()\u2500\u2500\u2500\u25B6\u2502 connecting \u2502 \u2502\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518 \u2502\n * \u2502 \u25B2 \u2502 \u2502\n * \u2502 \u2502 onopen \u2502\n * \u2502 \u2502 \u2502 \u2502\n * \u2502 \u2502 \u25BC \u2502\n * terminate() \u2502 \u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510 \u2502\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500stop()\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2502 ready \u2502\u2500\u2500\u2518\n * \u2502 \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n * \u2502 \u2502\n * \u2502 \u2502\n * \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n *\n * The `connecting` and `ready` state have a sub-state-machine for pausing.\n */\n\ntype Socket =\n | { state: \"disconnected\" }\n | { state: \"connecting\"; ws: WebSocket; paused: \"yes\" | \"no\" }\n | { state: \"ready\"; ws: WebSocket; paused: \"yes\" | \"no\" | \"uninitialized\" }\n | { state: \"stopped\" }\n | { state: \"terminated\" };\n\nexport type ReconnectMetadata = {\n connectionCount: number;\n lastCloseReason: string | null;\n clientTs: number;\n};\n\nexport type OnMessageResponse = {\n hasSyncedPastLastReconnect: boolean;\n};\n\nlet firstTime: number | undefined;\nfunction monotonicMillis() {\n if (firstTime === undefined) {\n firstTime = Date.now();\n }\n if (typeof performance === \"undefined\" || !performance.now) {\n return Date.now();\n }\n return Math.round(firstTime + performance.now());\n}\n\nfunction prettyNow() {\n return `t=${Math.round((monotonicMillis() - firstTime!) / 100) / 10}s`;\n}\n\nconst serverDisconnectErrors = {\n // A known error, e.g. during a restart or push\n InternalServerError: { timeout: 1000 },\n // ErrorMetadata::overloaded() messages that we realy should back off\n SubscriptionsWorkerFullError: { timeout: 3000 },\n TooManyConcurrentRequests: { timeout: 3000 },\n CommitterFullError: { timeout: 3000 },\n AwsTooManyRequestsException: { timeout: 3000 },\n ExecuteFullError: { timeout: 3000 },\n SystemTimeoutError: { timeout: 3000 },\n ExpiredInQueue: { timeout: 3000 },\n // ErrorMetadata::feature_temporarily_unavailable() that typically indicate a deploy just happened\n VectorIndexesUnavailable: { timeout: 1000 },\n SearchIndexesUnavailable: { timeout: 1000 },\n TableSummariesUnavailable: { timeout: 1000 },\n // More ErrorMetadata::overloaded()\n VectorIndexTooLarge: { timeout: 3000 },\n SearchIndexTooLarge: { timeout: 3000 },\n TooManyWritesInTimePeriod: { timeout: 3000 },\n} as const satisfies Record<string, { timeout: number }>;\n\ntype ServerDisconnectError = keyof typeof serverDisconnectErrors | \"Unknown\";\n\nfunction classifyDisconnectError(s?: string): ServerDisconnectError {\n if (s === undefined) return \"Unknown\";\n // startsWith so more info could be at the end (although currently there isn't)\n\n for (const prefix of Object.keys(\n serverDisconnectErrors,\n ) as ServerDisconnectError[]) {\n if (s.startsWith(prefix)) {\n return prefix;\n }\n }\n return \"Unknown\";\n}\n\n/**\n * A wrapper around a websocket that handles errors, reconnection, and message\n * parsing.\n */\nexport class WebSocketManager {\n private socket: Socket;\n\n private connectionCount: number;\n private _hasEverConnected: boolean = false;\n private lastCloseReason:\n | \"InitialConnect\"\n | \"OnCloseInvoked\"\n | (string & {}) // a full serverErrorReason (not just the prefix) or a new one\n | null;\n\n // State for assembling the split-up Transition currently being received.\n private transitionChunkBuffer: {\n chunks: string[];\n totalParts: number;\n transitionId: string;\n } | null = null;\n\n /** Upon HTTPS/WSS failure, the first jittered backoff duration, in ms. */\n private readonly defaultInitialBackoff: number;\n\n /** We backoff exponentially, but we need to cap that--this is the jittered max. */\n private readonly maxBackoff: number;\n\n /** How many times have we failed consecutively? */\n private retries: number;\n\n /** How long before lack of server response causes us to initiate a reconnect,\n * in ms */\n private readonly serverInactivityThreshold: number;\n\n private reconnectDueToServerInactivityTimeout: ReturnType<\n typeof setTimeout\n > | null;\n\n /** Scheduled reconnect state: timeout handle and timing info */\n private scheduledReconnect: {\n timeout: ReturnType<typeof setTimeout>;\n scheduledAt: number;\n backoffMs: number;\n } | null = null;\n\n private networkOnlineHandler: (() => void) | null = null;\n\n /** Pending event to send after reconnecting due to network recovery */\n private pendingNetworkRecoveryInfo: { timeSavedMs: number } | null = null;\n\n private readonly uri: string;\n private readonly onOpen: (reconnectMetadata: ReconnectMetadata) => void;\n private readonly onResume: () => void;\n private readonly onMessage: (message: ServerMessage) => OnMessageResponse;\n private readonly webSocketConstructor: typeof WebSocket;\n private readonly logger: Logger;\n private readonly onServerDisconnectError:\n | ((message: string) => void)\n | undefined;\n\n constructor(\n uri: string,\n callbacks: {\n onOpen: (reconnectMetadata: ReconnectMetadata) => void;\n onResume: () => void;\n onMessage: (message: ServerMessage) => OnMessageResponse;\n onServerDisconnectError?: ((message: string) => void) | undefined;\n },\n webSocketConstructor: typeof WebSocket,\n logger: Logger,\n private readonly markConnectionStateDirty: () => void,\n private readonly debug: boolean,\n ) {\n this.webSocketConstructor = webSocketConstructor;\n this.socket = { state: \"disconnected\" };\n this.connectionCount = 0;\n this.lastCloseReason = \"InitialConnect\";\n\n // backoff for unknown errors\n this.defaultInitialBackoff = 1000;\n this.maxBackoff = 16000;\n this.retries = 0;\n\n // Ping messages (sync protocol Pings, not WebSocket protocol Pings) are\n // sent every 15s in the absence of other messages. But a single large\n // Transition or other downstream message can hog the line so this\n // threshold is set higher to prevent clients from giving up.\n this.serverInactivityThreshold = 60000;\n this.reconnectDueToServerInactivityTimeout = null;\n\n this.uri = uri;\n this.onOpen = callbacks.onOpen;\n this.onResume = callbacks.onResume;\n this.onMessage = callbacks.onMessage;\n this.onServerDisconnectError = callbacks.onServerDisconnectError;\n this.logger = logger;\n\n // Set up network online event listener\n this.setupNetworkListener();\n\n this.connect();\n }\n\n private setSocketState(state: Socket) {\n this.socket = state;\n this._logVerbose(\n `socket state changed: ${this.socket.state}, paused: ${\n \"paused\" in this.socket ? this.socket.paused : undefined\n }`,\n );\n this.markConnectionStateDirty();\n }\n\n private setupNetworkListener() {\n // Only set up listener if we're in a browser environment with addEventListener\n // (React Native has window but not addEventListener)\n if (\n typeof window === \"undefined\" ||\n typeof window.addEventListener !== \"function\"\n ) {\n return;\n }\n // Avoid registering duplicate listeners\n if (this.networkOnlineHandler !== null) {\n return;\n }\n\n this.networkOnlineHandler = () => {\n this._logVerbose(\"network online event detected\");\n this.tryReconnectImmediately();\n };\n\n window.addEventListener(\"online\", this.networkOnlineHandler);\n this._logVerbose(\"network online event listener registered\");\n }\n\n private cleanupNetworkListener() {\n if (\n this.networkOnlineHandler &&\n typeof window !== \"undefined\" &&\n typeof window.removeEventListener === \"function\"\n ) {\n window.removeEventListener(\"online\", this.networkOnlineHandler);\n this.networkOnlineHandler = null;\n this._logVerbose(\"network online event listener removed\");\n }\n }\n\n private assembleTransition(chunk: TransitionChunk): Transition | null {\n if (\n chunk.partNumber < 0 ||\n chunk.partNumber >= chunk.totalParts ||\n chunk.totalParts === 0 ||\n (this.transitionChunkBuffer &&\n (this.transitionChunkBuffer.totalParts !== chunk.totalParts ||\n this.transitionChunkBuffer.transitionId !== chunk.transitionId))\n ) {\n // Throwing an error doesn't crash the client, so clear the buffer.\n this.transitionChunkBuffer = null;\n throw new Error(\"Invalid TransitionChunk\");\n }\n\n if (this.transitionChunkBuffer === null) {\n this.transitionChunkBuffer = {\n chunks: [],\n totalParts: chunk.totalParts,\n transitionId: chunk.transitionId,\n };\n }\n\n if (chunk.partNumber !== this.transitionChunkBuffer.chunks.length) {\n // Throwing an error doesn't crash the client, so clear the buffer.\n const expectedLength = this.transitionChunkBuffer.chunks.length;\n this.transitionChunkBuffer = null;\n throw new Error(\n `TransitionChunk received out of order: expected part ${expectedLength}, got ${chunk.partNumber}`,\n );\n }\n\n this.transitionChunkBuffer.chunks.push(chunk.chunk);\n\n if (this.transitionChunkBuffer.chunks.length === chunk.totalParts) {\n const fullJson = this.transitionChunkBuffer.chunks.join(\"\");\n this.transitionChunkBuffer = null;\n\n const transition = parseServerMessage(JSON.parse(fullJson));\n if (transition.type !== \"Transition\") {\n throw new Error(\n `Expected Transition, got ${transition.type} after assembling chunks`,\n );\n }\n return transition;\n }\n\n return null;\n }\n\n private connect() {\n if (this.socket.state === \"terminated\") {\n return;\n }\n if (\n this.socket.state !== \"disconnected\" &&\n this.socket.state !== \"stopped\"\n ) {\n throw new Error(\n \"Didn't start connection from disconnected state: \" + this.socket.state,\n );\n }\n\n const ws = new this.webSocketConstructor(this.uri);\n this._logVerbose(\"constructed WebSocket\");\n this.setSocketState({\n state: \"connecting\",\n ws,\n paused: \"no\",\n });\n\n // Kick off server inactivity timer before WebSocket connection is established\n // so we can detect cases where handshake fails.\n // The `onopen` event only fires after the connection is established:\n // Source: https://datatracker.ietf.org/doc/html/rfc6455#page-19:~:text=_The%20WebSocket%20Connection%20is%20Established_,-and\n this.resetServerInactivityTimeout();\n\n ws.onopen = () => {\n this.logger.logVerbose(\"begin ws.onopen\");\n if (this.socket.state !== \"connecting\") {\n throw new Error(\"onopen called with socket not in connecting state\");\n }\n this.setSocketState({\n state: \"ready\",\n ws,\n paused: this.socket.paused === \"yes\" ? \"uninitialized\" : \"no\",\n });\n this.resetServerInactivityTimeout();\n if (this.socket.paused === \"no\") {\n this._hasEverConnected = true;\n this.onOpen({\n connectionCount: this.connectionCount,\n lastCloseReason: this.lastCloseReason,\n clientTs: monotonicMillis(),\n });\n }\n\n if (this.lastCloseReason !== \"InitialConnect\") {\n if (this.lastCloseReason) {\n this.logger.log(\n \"WebSocket reconnected at\",\n prettyNow(),\n \"after disconnect due to\",\n this.lastCloseReason,\n );\n } else {\n this.logger.log(\"WebSocket reconnected at\", prettyNow());\n }\n }\n\n this.connectionCount += 1;\n this.lastCloseReason = null;\n\n // Send event for network recovery reconnect if applicable\n if (this.pendingNetworkRecoveryInfo !== null) {\n const { timeSavedMs } = this.pendingNetworkRecoveryInfo;\n this.pendingNetworkRecoveryInfo = null;\n this.sendMessage({\n type: \"Event\",\n eventType: \"NetworkRecoveryReconnect\",\n event: { timeSavedMs },\n });\n this.logger.log(\n `Network recovery reconnect saved ~${Math.round(timeSavedMs / 1000)}s of waiting`,\n );\n }\n };\n // NB: The WebSocket API calls `onclose` even if connection fails, so we can route all error paths through `onclose`.\n ws.onerror = (error) => {\n this.transitionChunkBuffer = null;\n const message = (error as ErrorEvent).message;\n if (message) {\n this.logger.log(`WebSocket error message: ${message}`);\n }\n };\n ws.onmessage = (message) => {\n this.resetServerInactivityTimeout();\n const messageLength = message.data.length;\n let serverMessage = parseServerMessage(JSON.parse(message.data));\n this._logVerbose(`received ws message with type ${serverMessage.type}`);\n\n // Ping's only purpose is to reset the server inactivity timer.\n if (serverMessage.type === \"Ping\") {\n return;\n }\n\n // TransitionChunks never reach the main client logic.\n if (serverMessage.type === \"TransitionChunk\") {\n const transition = this.assembleTransition(serverMessage);\n if (!transition) {\n return;\n }\n serverMessage = transition;\n this._logVerbose(\n `assembled full ws message of type ${serverMessage.type}`,\n );\n }\n\n if (this.transitionChunkBuffer !== null) {\n this.transitionChunkBuffer = null;\n this.logger.log(\n `Received unexpected ${serverMessage.type} while buffering TransitionChunks`,\n );\n }\n\n if (serverMessage.type === \"Transition\") {\n this.reportLargeTransition({\n messageLength,\n transition: serverMessage,\n });\n }\n const response = this.onMessage(serverMessage);\n if (response.hasSyncedPastLastReconnect) {\n // Reset backoff to 0 once all outstanding requests are complete.\n this.retries = 0;\n this.markConnectionStateDirty();\n }\n };\n ws.onclose = (event) => {\n this._logVerbose(\"begin ws.onclose\");\n this.transitionChunkBuffer = null;\n if (this.lastCloseReason === null) {\n // event.reason is often an empty string\n this.lastCloseReason = event.reason || `closed with code ${event.code}`;\n }\n if (\n event.code !== CLOSE_NORMAL &&\n event.code !== CLOSE_GOING_AWAY && // This commonly gets fired on mobile apps when the app is backgrounded\n event.code !== CLOSE_NO_STATUS &&\n event.code !== CLOSE_NOT_FOUND // Note that we want to retry on a 404, as it can be transient during a push.\n ) {\n let msg = `WebSocket closed with code ${event.code}`;\n if (event.reason) {\n msg += `: ${event.reason}`;\n }\n this.logger.log(msg);\n if (this.onServerDisconnectError && event.reason) {\n // This callback is a unstable API, InternalServerErrors in particular may be removed\n // since they reflect expected temporary downtime. But until a quantitative measure\n // of uptime is reported this unstable API errs on the inclusive side.\n this.onServerDisconnectError(msg);\n }\n }\n const reason = classifyDisconnectError(event.reason);\n this.scheduleReconnect(reason);\n return;\n };\n }\n\n /**\n * @returns The state of the {@link Socket}.\n */\n socketState(): string {\n return this.socket.state;\n }\n\n /**\n * @param message - A ClientMessage to send.\n * @returns Whether the message (might have been) sent.\n */\n sendMessage(message: ClientMessage) {\n const messageForLog = {\n type: message.type,\n ...(message.type === \"Authenticate\" && message.tokenType === \"User\"\n ? {\n value: `...${message.value.slice(-7)}`,\n }\n : {}),\n };\n if (this.socket.state === \"ready\" && this.socket.paused === \"no\") {\n const encodedMessage = encodeClientMessage(message);\n const request = JSON.stringify(encodedMessage);\n let sent = false;\n try {\n this.socket.ws.send(request);\n sent = true;\n } catch (error: any) {\n this.logger.log(\n `Failed to send message on WebSocket, reconnecting: ${error}`,\n );\n this.closeAndReconnect(\"FailedToSendMessage\");\n }\n this._logVerbose(\n `${sent ? \"sent\" : \"failed to send\"} message with type ${message.type}: ${JSON.stringify(\n messageForLog,\n )}`,\n );\n return true;\n }\n this._logVerbose(\n `message not sent (socket state: ${this.socket.state}, paused: ${\"paused\" in this.socket ? this.socket.paused : undefined}): ${JSON.stringify(\n messageForLog,\n )}`,\n );\n\n return false;\n }\n\n private resetServerInactivityTimeout() {\n if (this.socket.state === \"terminated\") {\n // Don't reset any timers if we were trying to terminate.\n return;\n }\n if (this.reconnectDueToServerInactivityTimeout !== null) {\n clearTimeout(this.reconnectDueToServerInactivityTimeout);\n this.reconnectDueToServerInactivityTimeout = null;\n }\n this.reconnectDueToServerInactivityTimeout = setTimeout(() => {\n this.closeAndReconnect(\"InactiveServer\");\n }, this.serverInactivityThreshold);\n }\n\n private scheduleReconnect(reason: \"client\" | ServerDisconnectError) {\n // Cancel any existing scheduled reconnect to avoid multiple reconnects\n if (this.scheduledReconnect) {\n clearTimeout(this.scheduledReconnect.timeout);\n this.scheduledReconnect = null;\n }\n\n this.socket = { state: \"disconnected\" };\n const backoff = this.nextBackoff(reason);\n this.markConnectionStateDirty();\n this.logger.log(`Attempting reconnect in ${Math.round(backoff)}ms`);\n\n const scheduledAt = monotonicMillis();\n const timeoutId = setTimeout(() => {\n // Only proceed if this timeout hasn't been cleared\n if (this.scheduledReconnect?.timeout === timeoutId) {\n this.scheduledReconnect = null;\n this.connect();\n }\n }, backoff);\n\n this.scheduledReconnect = {\n timeout: timeoutId,\n scheduledAt,\n backoffMs: backoff,\n };\n }\n\n /**\n * Close the WebSocket and schedule a reconnect.\n *\n * This should be used when we hit an error and would like to restart the session.\n */\n private closeAndReconnect(closeReason: string) {\n this._logVerbose(`begin closeAndReconnect with reason ${closeReason}`);\n switch (this.socket.state) {\n case \"disconnected\":\n case \"terminated\":\n case \"stopped\":\n // Nothing to do if we don't have a WebSocket.\n return;\n case \"connecting\":\n case \"ready\": {\n this.lastCloseReason = closeReason;\n // Close the old socket asynchronously, we'll open a new socket in reconnect.\n void this.close();\n this.scheduleReconnect(\"client\");\n return;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n this.socket satisfies never;\n }\n }\n }\n\n /**\n * Close the WebSocket, being careful to clear the onclose handler to avoid re-entrant\n * calls. Use this instead of directly calling `ws.close()`\n *\n * It is the callers responsibility to update the state after this method is called so that the\n * closed socket is not accessible or used again after this method is called\n */\n private close(): Promise<void> {\n this.transitionChunkBuffer = null;\n switch (this.socket.state) {\n case \"disconnected\":\n case \"terminated\":\n case \"stopped\":\n // Nothing to do if we don't have a WebSocket.\n return Promise.resolve();\n case \"connecting\": {\n const ws = this.socket.ws;\n // Messages can still be received after close but we're not interested.\n ws.onmessage = (_message) => {\n this._logVerbose(\"Ignoring message received after close\");\n };\n return new Promise((r) => {\n ws.onclose = () => {\n this._logVerbose(\"Closed after connecting\");\n r();\n };\n ws.onopen = () => {\n this._logVerbose(\"Opened after connecting\");\n ws.close();\n };\n });\n }\n case \"ready\": {\n this._logVerbose(\"ws.close called\");\n const ws = this.socket.ws;\n // Messages can still be received after close but we're not interested.\n ws.onmessage = (_message) => {\n this._logVerbose(\"Ignoring message received after close\");\n };\n const result: Promise<void> = new Promise((r) => {\n ws.onclose = () => {\n r();\n };\n });\n ws.close();\n return result;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n this.socket satisfies never;\n return Promise.resolve();\n }\n }\n }\n\n /**\n * Close the WebSocket and do not reconnect.\n * @returns A Promise that resolves when the WebSocket `onClose` callback is called.\n */\n terminate(): Promise<void> {\n if (this.reconnectDueToServerInactivityTimeout) {\n clearTimeout(this.reconnectDueToServerInactivityTimeout);\n }\n if (this.scheduledReconnect) {\n clearTimeout(this.scheduledReconnect.timeout);\n this.scheduledReconnect = null;\n }\n this.cleanupNetworkListener();\n switch (this.socket.state) {\n case \"terminated\":\n case \"stopped\":\n case \"disconnected\":\n case \"connecting\":\n case \"ready\": {\n const result = this.close();\n this.setSocketState({ state: \"terminated\" });\n return result;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n this.socket satisfies never;\n throw new Error(\n `Invalid websocket state: ${(this.socket as any).state}`,\n );\n }\n }\n }\n\n stop(): Promise<void> {\n switch (this.socket.state) {\n case \"terminated\":\n // If we're terminating we ignore stop\n return Promise.resolve();\n case \"connecting\":\n case \"stopped\":\n case \"disconnected\":\n case \"ready\": {\n this.cleanupNetworkListener();\n const result = this.close();\n this.socket = { state: \"stopped\" };\n return result;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n this.socket satisfies never;\n return Promise.resolve();\n }\n }\n }\n\n /**\n * Create a new WebSocket after a previous `stop()`, unless `terminate()` was\n * called before.\n */\n tryRestart(): void {\n switch (this.socket.state) {\n case \"stopped\":\n break;\n case \"terminated\":\n case \"connecting\":\n case \"ready\":\n case \"disconnected\":\n this.logger.logVerbose(\"Restart called without stopping first\");\n return;\n default: {\n // Enforce that the switch-case is exhaustive.\n this.socket satisfies never;\n }\n }\n this.setupNetworkListener();\n this.connect();\n }\n\n pause(): void {\n switch (this.socket.state) {\n case \"disconnected\":\n case \"stopped\":\n case \"terminated\":\n // If already stopped or stopping ignore.\n return;\n case \"connecting\":\n case \"ready\": {\n this.socket = { ...this.socket, paused: \"yes\" };\n return;\n }\n default: {\n // Enforce that the switch-case is exhaustive.\n this.socket satisfies never;\n return;\n }\n }\n }\n\n /**\n * Try to reconnect immediately, canceling any scheduled reconnect.\n * This is useful when detecting network recovery.\n * Only takes action if we're in disconnected state (waiting to reconnect).\n */\n tryReconnectImmediately(): void {\n this._logVerbose(\"tryReconnectImmediately called\");\n\n // Only reconnect if we're in disconnected state (waiting to reconnect)\n if (this.socket.state !== \"disconnected\") {\n this._logVerbose(\n `tryReconnectImmediately called but socket state is ${this.socket.state}, no action taken`,\n );\n return;\n }\n\n // Track how much time we saved by reconnecting immediately\n let timeSavedMs: number | null = null;\n if (this.scheduledReconnect) {\n const elapsed = monotonicMillis() - this.scheduledReconnect.scheduledAt;\n timeSavedMs = Math.max(0, this.scheduledReconnect.backoffMs - elapsed);\n this._logVerbose(\n `would have waited ${Math.round(timeSavedMs)}ms more (backoff was ${Math.round(this.scheduledReconnect.backoffMs)}ms, elapsed ${Math.round(elapsed)}ms)`,\n );\n // Cancel the scheduled reconnect\n clearTimeout(this.scheduledReconnect.timeout);\n this.scheduledReconnect = null;\n this._logVerbose(\"canceled scheduled reconnect\");\n }\n\n this.logger.log(\"Network recovery detected, reconnecting immediately\");\n // Store the time saved to send as an event after we connect\n this.pendingNetworkRecoveryInfo =\n timeSavedMs !== null ? { timeSavedMs } : null;\n this.connect();\n }\n\n /**\n * Resume the state machine if previously paused.\n */\n resume(): void {\n switch (this.socket.state) {\n case \"connecting\":\n this.socket = { ...this.socket, paused: \"no\" };\n return;\n case \"ready\":\n if (this.socket.paused === \"uninitialized\") {\n this.socket = { ...this.socket, paused: \"no\" };\n this.onOpen({\n connectionCount: this.connectionCount,\n lastCloseReason: this.lastCloseReason,\n clientTs: monotonicMillis(),\n });\n } else if (this.socket.paused === \"yes\") {\n this.socket = { ...this.socket, paused: \"no\" };\n this.onResume();\n }\n return;\n case \"terminated\":\n case \"stopped\":\n case \"disconnected\":\n // Ignore resume if not paused, perhaps we already resumed.\n return;\n default: {\n // Enforce that the switch-case is exhaustive.\n this.socket satisfies never;\n }\n }\n this.connect();\n }\n\n connectionState(): {\n isConnected: boolean;\n hasEverConnected: boolean;\n connectionCount: number;\n connectionRetries: number;\n } {\n return {\n isConnected: this.socket.state === \"ready\",\n hasEverConnected: this._hasEverConnected,\n connectionCount: this.connectionCount,\n connectionRetries: this.retries,\n };\n }\n\n private _logVerbose(message: string) {\n this.logger.logVerbose(message);\n }\n\n private nextBackoff(reason: \"client\" | ServerDisconnectError): number {\n const initialBackoff: number =\n reason === \"client\"\n ? 100 // There's no evidence of a server problem, retry quickly\n : reason === \"Unknown\"\n ? this.defaultInitialBackoff\n : serverDisconnectErrors[reason].timeout;\n\n const baseBackoff = initialBackoff * Math.pow(2, this.retries);\n this.retries += 1;\n const actualBackoff = Math.min(baseBackoff, this.maxBackoff);\n const jitter = actualBackoff * (Math.random() - 0.5);\n return actualBackoff + jitter;\n }\n\n private reportLargeTransition({\n transition,\n messageLength,\n }: {\n transition: Transition;\n messageLength: number;\n }) {\n if (\n transition.clientClockSkew === undefined ||\n transition.serverTs === undefined\n ) {\n return;\n }\n\n const transitionTransitTime =\n monotonicMillis() - // client time now\n // clientClockSkew = (server time + upstream latency) - client time\n // clientClockSkew is \"how many milliseconds behind (slow) is the client clock\"\n // but the latency of the Connect message inflates this, making it appear further behind\n transition.clientClockSkew -\n transition.serverTs / 1_000_000; // server time when transition was sent\n const prettyTransitionTime = `${Math.round(transitionTransitTime)}ms`;\n const prettyMessageMB = `${Math.round(messageLength / 10_000) / 100}MB`;\n const bytesPerSecond = messageLength / (transitionTransitTime / 1000);\n const prettyBytesPerSecond = `${Math.round(bytesPerSecond / 10_000) / 100}MB per second`;\n this._logVerbose(\n `received ${prettyMessageMB} transition in ${prettyTransitionTime} at ${prettyBytesPerSecond}`,\n );\n\n // Warnings that will show up for *all users*, so don't be too aggressive.\n // These can be silenced (along with reconnection messages) by setting `logger: false` in client options.\n if (messageLength > 20_000_000) {\n // Big enough that the developer should be made aware of this.\n this.logger.log(\n `received query results totaling more that 20MB (${prettyMessageMB}) which will take a long time to download on slower connections`,\n );\n } else if (transitionTransitTime > 20_000) {\n // Long enough that a pattern of these should be interesting to a developer, but be aware that\n // weak connections, putting clients to sleep, backgrounding etc. could all cause this too.\n this.logger.log(\n `received query results totaling ${prettyMessageMB} which took more than 20s to arrive (${prettyTransitionTime})`,\n );\n }\n\n if (this.debug) {\n // debug means \"reportDebugInfoToConvex\" is set so this can be aggressive.\n this.sendMessage({\n type: \"Event\",\n eventType: \"ClientReceivedTransition\",\n event: { transitionTransitTime, messageLength },\n });\n }\n }\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,58 +1,76 @@
1
1
  "use strict";
2
2
  import path from "path";
3
3
  import { Command } from "@commander-js/extra-typings";
4
+ import { chalkStderr } from "chalk";
5
+ import { logMessage } from "../bundler/log.js";
4
6
  import { oneoffContext } from "../bundler/context.js";
5
7
  import { readProjectConfig } from "./lib/config.js";
6
8
  import { functionsDir } from "./lib/utils/utils.js";
7
9
  import {
8
- updateAiFiles,
10
+ installAiFiles,
9
11
  enableAiFiles,
10
- removeAiFiles,
11
12
  disableAiFiles,
12
- statusAiFiles
13
- } from "./lib/ai/index.js";
13
+ removeAiFiles
14
+ } from "./lib/aiFiles/index.js";
15
+ import { statusAiFiles } from "./lib/aiFiles/status.js";
16
+ import { writeAiFilesConfig } from "./lib/config.js";
14
17
  async function resolveProjectPaths() {
15
18
  const ctx = await oneoffContext({});
16
19
  const { configPath, projectConfig } = await readProjectConfig(ctx);
17
20
  const convexDir = path.resolve(functionsDir(configPath, projectConfig));
18
21
  const projectDir = path.resolve(path.dirname(configPath));
19
- return { projectDir, convexDir };
22
+ const aiFilesConfig = projectConfig.aiFiles;
23
+ return { projectDir, convexDir, aiFilesConfig, projectConfig };
20
24
  }
21
25
  const aiInstall = new Command("install").summary("Install or refresh Convex AI files").description(
22
- "Installs the following (or refreshes them if already present):\n - convex/_generated/ai/guidelines.md\n - AGENTS.md (Convex section only)\n - CLAUDE.md (Convex section only)\n - Agent skills (installed to each coding agent's native path)\n\nFiles you have edited yourself are detected via content hashing and skipped\nrather than silently overwritten."
26
+ "Installs the following (or refreshes them if already present):\n - convex/_generated/ai/guidelines.md\n - AGENTS.md (Convex section only)\n - CLAUDE.md (Convex section only)\n - Agent skills (installed to each coding agent's native path, configured via convex.json)"
23
27
  ).allowExcessArguments(false).action(async () => {
24
- const { projectDir, convexDir } = await resolveProjectPaths();
25
- await updateAiFiles(projectDir, convexDir);
28
+ const { projectDir, convexDir, aiFilesConfig } = await resolveProjectPaths();
29
+ await installAiFiles({ projectDir, convexDir, aiFilesConfig });
30
+ logMessage(`${chalkStderr.green("\u2714")} Convex AI files installed.`);
26
31
  });
27
- const aiEnable = new Command("enable").summary("Enable Convex AI file staleness/install messages").description(
28
- "Re-enables AI file staleness/install messages by writing\n`aiFiles.disableStalenessMessage: false` to `convex.json`, then installs\nor refreshes the managed AI files."
32
+ const aiEnable = new Command("enable").summary("Enable Convex AI files").description(
33
+ "Re-enables Convex AI files by writing `aiFiles.enabled: true` to\n`convex.json`, then installs or refreshes the managed AI files."
29
34
  ).allowExcessArguments(false).action(async () => {
30
- const { projectDir, convexDir } = await resolveProjectPaths();
31
- await enableAiFiles(projectDir, convexDir);
35
+ const { projectDir, convexDir, aiFilesConfig } = await resolveProjectPaths();
36
+ const newAiFilesConfig = await enableAiFiles({
37
+ projectDir,
38
+ convexDir,
39
+ aiFilesConfig
40
+ });
41
+ await writeAiFilesConfig({ projectDir, aiFiles: newAiFilesConfig });
32
42
  });
33
43
  const aiUpdate = new Command("update").summary("Update Convex AI files to the latest version").description(
34
- "Updates the following files if they exist and have not been locally modified:\n - convex/_generated/ai/guidelines.md\n - AGENTS.md (Convex section only)\n - CLAUDE.md (Convex section only)\n - Agent skills (installed to each coding agent's native path)\n\nFiles you have edited yourself are detected via content hashing and skipped\nrather than silently overwritten.\n\nDoes not change `aiFiles.disableStalenessMessage` in `convex.json`.\nRun `npx convex ai-files enable` to enable staleness/install messages."
44
+ "Updates the following to their latest versions:\n - convex/_generated/ai/guidelines.md\n - AGENTS.md (Convex section only)\n - CLAUDE.md (Convex section only)\n - Agent skills (installed to each coding agent's native path, configured via convex.json)\n\n"
35
45
  ).allowExcessArguments(false).action(async () => {
36
- const { projectDir, convexDir } = await resolveProjectPaths();
37
- await updateAiFiles(projectDir, convexDir);
46
+ const { projectDir, convexDir, aiFilesConfig } = await resolveProjectPaths();
47
+ await installAiFiles({ projectDir, convexDir, aiFilesConfig });
48
+ logMessage(`${chalkStderr.green("\u2714")} Convex AI files updated.`);
38
49
  });
39
- const aiDisable = new Command("disable").summary("Suppress AI file staleness/install messages without removing files").description(
40
- "Writes a suppression marker into `convex.json` (`aiFiles.disableStalenessMessage`) so\n`npx convex dev` stops suggesting you install AI files.\n\nFiles already installed are left untouched - use `npx convex ai-files remove`\nif you also want to delete them.\n\nRun `npx convex ai-files enable` to re-enable at any time."
50
+ const aiDisable = new Command("disable").summary("Disable Convex AI files without removing them").description(
51
+ "Writes `aiFiles.enabled: false` to `convex.json` so `npx convex dev`\nstops prompting to install AI files and suppresses staleness messages.\n\nFiles already installed are left untouched - use `npx convex ai-files remove`\nif you also want to delete them.\n\nRun `npx convex ai-files enable` to re-enable at any time."
41
52
  ).allowExcessArguments(false).action(async () => {
42
- const { projectDir } = await resolveProjectPaths();
43
- await disableAiFiles(projectDir);
53
+ const { projectDir, aiFilesConfig } = await resolveProjectPaths();
54
+ const newAiFilesConfig = disableAiFiles(aiFilesConfig);
55
+ await writeAiFilesConfig({
56
+ projectDir,
57
+ aiFiles: newAiFilesConfig
58
+ });
59
+ logMessage(
60
+ `${chalkStderr.green(`\u2714`)} Convex AI files disabled. Run ${chalkStderr.bold(`npx convex ai-files enable`)} to re-enable.`
61
+ );
44
62
  });
45
63
  const aiStatus = new Command("status").summary("Show the current status of Convex AI files").description(
46
64
  "Prints whether Convex AI files are enabled, and for each component:\n - convex/_generated/ai/guidelines.md\n - AGENTS.md (Convex section)\n - CLAUDE.md (if installed by Convex)\n - Agent skills\n\nFetches the latest hashes from version.convex.dev to report whether\neach file is up to date. If the network is unavailable the staleness\ncheck is skipped silently."
47
65
  ).allowExcessArguments(false).action(async () => {
48
- const { projectDir, convexDir } = await resolveProjectPaths();
49
- await statusAiFiles(projectDir, convexDir);
66
+ const { projectDir, convexDir, aiFilesConfig } = await resolveProjectPaths();
67
+ await statusAiFiles({ projectDir, convexDir, aiFilesConfig });
50
68
  });
51
69
  const aiRemove = new Command("remove").summary("Remove all Convex AI files from the project").description(
52
- "Removes the following:\n - convex/_generated/ai/ directory (guidelines.md, ai-files.state.json)\n - Convex sections from AGENTS.md and CLAUDE.md\n - Agent skills installed by `convex ai-files install`\n\nIf removing the managed section leaves AGENTS.md or CLAUDE.md empty, the\nempty file is deleted. Otherwise the rest of the file is kept.\n\nSkills installed from other sources are not affected.\n\nNote: after `remove`, `npx convex dev` will suggest reinstalling AI files.\nUse `npx convex ai-files disable` to suppress that prompt without deleting files."
70
+ "Removes the following:\n - convex/_generated/ai/ directory (guidelines.md, ai-files.state.json)\n - Convex sections from AGENTS.md and CLAUDE.md\n - Agent skills installed by `convex ai-files install`\n\nIf removing the managed section leaves AGENTS.md or CLAUDE.md empty, the\nempty file is deleted. Otherwise the rest of the file is kept.\n\nSkills installed from other sources are not affected.\n\nNote: after `remove`, `npx convex dev` will suggest reinstalling AI files.\nUse `npx convex ai-files disable` to opt out entirely without deleting files."
53
71
  ).allowExcessArguments(false).action(async () => {
54
72
  const { projectDir, convexDir } = await resolveProjectPaths();
55
- await removeAiFiles(projectDir, convexDir);
73
+ await removeAiFiles({ projectDir, convexDir });
56
74
  });
57
75
  export const aiFiles = new Command("ai-files").summary("Manage Convex AI files").description(
58
76
  "Convex AI files help AI coding assistants (Cursor, Claude Code, etc.) understand\nConvex patterns and APIs. They are set up during your first `npx convex dev`\nand can be managed at any time with the commands below."