convex 1.42.2 → 1.43.0-alpha.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 (276) hide show
  1. package/CHANGELOG.md +8 -1
  2. package/bin/main-dev +6 -1
  3. package/dist/browser.bundle.js +49 -10
  4. package/dist/browser.bundle.js.map +2 -2
  5. package/dist/cjs/browser/index.js.map +2 -2
  6. package/dist/cjs/browser/simple_client-node.js +35 -35
  7. package/dist/cjs/browser/simple_client-node.js.map +2 -2
  8. package/dist/cjs/browser/sync/client.js.map +1 -1
  9. package/dist/cjs/browser/sync/paginated_query_client.js +20 -9
  10. package/dist/cjs/browser/sync/paginated_query_client.js.map +2 -2
  11. package/dist/cjs/cli/codegen_templates/component_api.js +2 -2
  12. package/dist/cjs/cli/codegen_templates/component_api.js.map +2 -2
  13. package/dist/cjs/cli/codegen_templates/validator_helpers.js +2 -0
  14. package/dist/cjs/cli/codegen_templates/validator_helpers.js.map +2 -2
  15. package/dist/cjs/cli/deployment.js +2 -1
  16. package/dist/cjs/cli/deployment.js.map +2 -2
  17. package/dist/cjs/cli/deploymentCreate.js.map +2 -2
  18. package/dist/cjs/cli/generatedApi.js.map +1 -1
  19. package/dist/cjs/cli/lib/deployApi/validator.js +1 -0
  20. package/dist/cjs/cli/lib/deployApi/validator.js.map +2 -2
  21. package/dist/cjs/cli/lib/deploymentSelection.js.map +2 -2
  22. package/dist/cjs/cli/lib/generateDocs.js +1 -1
  23. package/dist/cjs/cli/lib/generateDocs.js.map +2 -2
  24. package/dist/cjs/cli/lib/generatedFunctionLogsApi.js.map +1 -1
  25. package/dist/cjs/cli/lib/localDeployment/projectMismatch.js.map +2 -2
  26. package/dist/cjs/cli/lib/login.js +23 -23
  27. package/dist/cjs/cli/lib/login.js.map +2 -2
  28. package/dist/cjs/cli/lib/typecheck.js +28 -25
  29. package/dist/cjs/cli/lib/typecheck.js.map +2 -2
  30. package/dist/cjs/cli/lib/usageLimits.js +164 -0
  31. package/dist/cjs/cli/lib/usageLimits.js.map +7 -0
  32. package/dist/cjs/cli/usageLimits.js +326 -0
  33. package/dist/cjs/cli/usageLimits.js.map +7 -0
  34. package/dist/cjs/index.js +1 -1
  35. package/dist/cjs/index.js.map +1 -1
  36. package/dist/cjs/react/client.js.map +2 -2
  37. package/dist/cjs/server/components/index.js.map +2 -2
  38. package/dist/cjs/server/cron.js +33 -29
  39. package/dist/cjs/server/cron.js.map +2 -2
  40. package/dist/cjs/server/database.js.map +1 -1
  41. package/dist/cjs/server/impl/database_impl.js +3 -0
  42. package/dist/cjs/server/impl/database_impl.js.map +2 -2
  43. package/dist/cjs/server/log.js +2 -1
  44. package/dist/cjs/server/log.js.map +2 -2
  45. package/dist/cjs/server/logVars.js +12 -1
  46. package/dist/cjs/server/logVars.js.map +2 -2
  47. package/dist/cjs/server/meta.js.map +1 -1
  48. package/dist/cjs/values/compare.js +5 -1
  49. package/dist/cjs/values/compare.js.map +2 -2
  50. package/dist/cjs/values/index.js +1 -0
  51. package/dist/cjs/values/index.js.map +2 -2
  52. package/dist/cjs/values/size.js +4 -0
  53. package/dist/cjs/values/size.js.map +2 -2
  54. package/dist/cjs/values/validator.js +11 -0
  55. package/dist/cjs/values/validator.js.map +2 -2
  56. package/dist/cjs/values/validators.js +20 -0
  57. package/dist/cjs/values/validators.js.map +2 -2
  58. package/dist/cjs/values/value.js +31 -0
  59. package/dist/cjs/values/value.js.map +2 -2
  60. package/dist/cjs-types/browser/index.d.ts.map +1 -1
  61. package/dist/cjs-types/browser/sync/paginated_query_client.d.ts +1 -38
  62. package/dist/cjs-types/browser/sync/paginated_query_client.d.ts.map +1 -1
  63. package/dist/cjs-types/cli/codegen_templates/component_api.d.ts.map +1 -1
  64. package/dist/cjs-types/cli/codegen_templates/validator_helpers.d.ts.map +1 -1
  65. package/dist/cjs-types/cli/codegen_templates/validator_helpers.test.d.ts +2 -0
  66. package/dist/cjs-types/cli/codegen_templates/validator_helpers.test.d.ts.map +1 -0
  67. package/dist/cjs-types/cli/deployment.d.ts.map +1 -1
  68. package/dist/cjs-types/cli/deploymentCreate.d.ts +2 -2
  69. package/dist/cjs-types/cli/generatedApi.d.ts +43 -43
  70. package/dist/cjs-types/cli/generatedApi.d.ts.map +1 -1
  71. package/dist/cjs-types/cli/lib/deployApi/validator.d.ts +6 -0
  72. package/dist/cjs-types/cli/lib/deployApi/validator.d.ts.map +1 -1
  73. package/dist/cjs-types/cli/lib/deploymentSelection.d.ts +2 -2
  74. package/dist/cjs-types/cli/lib/deploymentSelection.d.ts.map +1 -1
  75. package/dist/cjs-types/cli/lib/generatedFunctionLogsApi.d.ts +45 -45
  76. package/dist/cjs-types/cli/lib/generatedFunctionLogsApi.d.ts.map +1 -1
  77. package/dist/cjs-types/cli/lib/localDeployment/projectMismatch.d.ts +2 -2
  78. package/dist/cjs-types/cli/lib/localDeployment/projectMismatch.d.ts.map +1 -1
  79. package/dist/cjs-types/cli/lib/login.d.ts.map +1 -1
  80. package/dist/cjs-types/cli/lib/mcp/tools/insights.d.ts +24 -24
  81. package/dist/cjs-types/cli/lib/typecheck.d.ts +2 -0
  82. package/dist/cjs-types/cli/lib/typecheck.d.ts.map +1 -1
  83. package/dist/cjs-types/cli/lib/typecheck.test.d.ts +2 -0
  84. package/dist/cjs-types/cli/lib/typecheck.test.d.ts.map +1 -0
  85. package/dist/cjs-types/cli/lib/usageLimits.d.ts +46 -0
  86. package/dist/cjs-types/cli/lib/usageLimits.d.ts.map +1 -0
  87. package/dist/cjs-types/cli/usageLimits.d.ts +22 -0
  88. package/dist/cjs-types/cli/usageLimits.d.ts.map +1 -0
  89. package/dist/cjs-types/index.d.ts +1 -1
  90. package/dist/cjs-types/index.d.ts.map +1 -1
  91. package/dist/cjs-types/react/client.d.ts.map +1 -1
  92. package/dist/cjs-types/server/components/index.d.ts +26 -8
  93. package/dist/cjs-types/server/components/index.d.ts.map +1 -1
  94. package/dist/cjs-types/server/cron.d.ts +20 -11
  95. package/dist/cjs-types/server/cron.d.ts.map +1 -1
  96. package/dist/cjs-types/server/cron.test.d.ts +2 -0
  97. package/dist/cjs-types/server/cron.test.d.ts.map +1 -0
  98. package/dist/cjs-types/server/database.d.ts.map +1 -1
  99. package/dist/cjs-types/server/impl/database_impl.d.ts.map +1 -1
  100. package/dist/cjs-types/server/log.d.ts.map +1 -1
  101. package/dist/cjs-types/server/logVars.d.ts +14 -1
  102. package/dist/cjs-types/server/logVars.d.ts.map +1 -1
  103. package/dist/cjs-types/server/meta.d.ts +2 -2
  104. package/dist/cjs-types/server/meta.d.ts.map +1 -1
  105. package/dist/cjs-types/values/compare.d.ts.map +1 -1
  106. package/dist/cjs-types/values/index.d.ts +2 -2
  107. package/dist/cjs-types/values/index.d.ts.map +1 -1
  108. package/dist/cjs-types/values/size.d.ts +1 -1
  109. package/dist/cjs-types/values/size.d.ts.map +1 -1
  110. package/dist/cjs-types/values/validator.d.ts +10 -1
  111. package/dist/cjs-types/values/validator.d.ts.map +1 -1
  112. package/dist/cjs-types/values/validators.d.ts +14 -3
  113. package/dist/cjs-types/values/validators.d.ts.map +1 -1
  114. package/dist/cjs-types/values/value.d.ts +18 -1
  115. package/dist/cjs-types/values/value.d.ts.map +1 -1
  116. package/dist/cli.bundle.cjs +2327 -1866
  117. package/dist/cli.bundle.cjs.map +4 -4
  118. package/dist/esm/browser/index.js.map +2 -2
  119. package/dist/esm/browser/simple_client-node.js +35 -35
  120. package/dist/esm/browser/simple_client-node.js.map +1 -1
  121. package/dist/esm/browser/sync/client.js.map +1 -1
  122. package/dist/esm/browser/sync/paginated_query_client.js +20 -9
  123. package/dist/esm/browser/sync/paginated_query_client.js.map +2 -2
  124. package/dist/esm/cli/codegen_templates/component_api.js +8 -3
  125. package/dist/esm/cli/codegen_templates/component_api.js.map +2 -2
  126. package/dist/esm/cli/codegen_templates/validator_helpers.js +2 -0
  127. package/dist/esm/cli/codegen_templates/validator_helpers.js.map +2 -2
  128. package/dist/esm/cli/deployment.js +2 -1
  129. package/dist/esm/cli/deployment.js.map +2 -2
  130. package/dist/esm/cli/deploymentCreate.js.map +2 -2
  131. package/dist/esm/cli/lib/deployApi/validator.js +1 -0
  132. package/dist/esm/cli/lib/deployApi/validator.js.map +2 -2
  133. package/dist/esm/cli/lib/deploymentSelection.js.map +2 -2
  134. package/dist/esm/cli/lib/generateDocs.js +1 -1
  135. package/dist/esm/cli/lib/generateDocs.js.map +2 -2
  136. package/dist/esm/cli/lib/localDeployment/projectMismatch.js.map +2 -2
  137. package/dist/esm/cli/lib/login.js +23 -23
  138. package/dist/esm/cli/lib/login.js.map +2 -2
  139. package/dist/esm/cli/lib/typecheck.js +27 -25
  140. package/dist/esm/cli/lib/typecheck.js.map +2 -2
  141. package/dist/esm/cli/lib/usageLimits.js +129 -0
  142. package/dist/esm/cli/lib/usageLimits.js.map +7 -0
  143. package/dist/esm/cli/usageLimits.js +316 -0
  144. package/dist/esm/cli/usageLimits.js.map +7 -0
  145. package/dist/esm/index.js +1 -1
  146. package/dist/esm/index.js.map +1 -1
  147. package/dist/esm/react/client.js.map +2 -2
  148. package/dist/esm/server/components/index.js.map +2 -2
  149. package/dist/esm/server/cron.js +33 -29
  150. package/dist/esm/server/cron.js.map +2 -2
  151. package/dist/esm/server/impl/database_impl.js +4 -1
  152. package/dist/esm/server/impl/database_impl.js.map +2 -2
  153. package/dist/esm/server/log.js +3 -2
  154. package/dist/esm/server/log.js.map +2 -2
  155. package/dist/esm/server/logVars.js +11 -1
  156. package/dist/esm/server/logVars.js.map +2 -2
  157. package/dist/esm/values/compare.js +5 -1
  158. package/dist/esm/values/compare.js.map +2 -2
  159. package/dist/esm/values/index.js +1 -1
  160. package/dist/esm/values/index.js.map +2 -2
  161. package/dist/esm/values/size.js +4 -0
  162. package/dist/esm/values/size.js.map +2 -2
  163. package/dist/esm/values/validator.js +12 -0
  164. package/dist/esm/values/validator.js.map +2 -2
  165. package/dist/esm/values/validators.js +19 -0
  166. package/dist/esm/values/validators.js.map +2 -2
  167. package/dist/esm/values/value.js +29 -1
  168. package/dist/esm/values/value.js.map +2 -2
  169. package/dist/esm-types/browser/index.d.ts.map +1 -1
  170. package/dist/esm-types/browser/sync/paginated_query_client.d.ts +1 -38
  171. package/dist/esm-types/browser/sync/paginated_query_client.d.ts.map +1 -1
  172. package/dist/esm-types/cli/codegen_templates/component_api.d.ts.map +1 -1
  173. package/dist/esm-types/cli/codegen_templates/validator_helpers.d.ts.map +1 -1
  174. package/dist/esm-types/cli/codegen_templates/validator_helpers.test.d.ts +2 -0
  175. package/dist/esm-types/cli/codegen_templates/validator_helpers.test.d.ts.map +1 -0
  176. package/dist/esm-types/cli/deployment.d.ts.map +1 -1
  177. package/dist/esm-types/cli/deploymentCreate.d.ts +2 -2
  178. package/dist/esm-types/cli/generatedApi.d.ts +43 -43
  179. package/dist/esm-types/cli/generatedApi.d.ts.map +1 -1
  180. package/dist/esm-types/cli/lib/deployApi/validator.d.ts +6 -0
  181. package/dist/esm-types/cli/lib/deployApi/validator.d.ts.map +1 -1
  182. package/dist/esm-types/cli/lib/deploymentSelection.d.ts +2 -2
  183. package/dist/esm-types/cli/lib/deploymentSelection.d.ts.map +1 -1
  184. package/dist/esm-types/cli/lib/generatedFunctionLogsApi.d.ts +45 -45
  185. package/dist/esm-types/cli/lib/generatedFunctionLogsApi.d.ts.map +1 -1
  186. package/dist/esm-types/cli/lib/localDeployment/projectMismatch.d.ts +2 -2
  187. package/dist/esm-types/cli/lib/localDeployment/projectMismatch.d.ts.map +1 -1
  188. package/dist/esm-types/cli/lib/login.d.ts.map +1 -1
  189. package/dist/esm-types/cli/lib/mcp/tools/insights.d.ts +24 -24
  190. package/dist/esm-types/cli/lib/typecheck.d.ts +2 -0
  191. package/dist/esm-types/cli/lib/typecheck.d.ts.map +1 -1
  192. package/dist/esm-types/cli/lib/typecheck.test.d.ts +2 -0
  193. package/dist/esm-types/cli/lib/typecheck.test.d.ts.map +1 -0
  194. package/dist/esm-types/cli/lib/usageLimits.d.ts +46 -0
  195. package/dist/esm-types/cli/lib/usageLimits.d.ts.map +1 -0
  196. package/dist/esm-types/cli/usageLimits.d.ts +22 -0
  197. package/dist/esm-types/cli/usageLimits.d.ts.map +1 -0
  198. package/dist/esm-types/index.d.ts +1 -1
  199. package/dist/esm-types/index.d.ts.map +1 -1
  200. package/dist/esm-types/react/client.d.ts.map +1 -1
  201. package/dist/esm-types/server/components/index.d.ts +26 -8
  202. package/dist/esm-types/server/components/index.d.ts.map +1 -1
  203. package/dist/esm-types/server/cron.d.ts +20 -11
  204. package/dist/esm-types/server/cron.d.ts.map +1 -1
  205. package/dist/esm-types/server/cron.test.d.ts +2 -0
  206. package/dist/esm-types/server/cron.test.d.ts.map +1 -0
  207. package/dist/esm-types/server/database.d.ts.map +1 -1
  208. package/dist/esm-types/server/impl/database_impl.d.ts.map +1 -1
  209. package/dist/esm-types/server/log.d.ts.map +1 -1
  210. package/dist/esm-types/server/logVars.d.ts +14 -1
  211. package/dist/esm-types/server/logVars.d.ts.map +1 -1
  212. package/dist/esm-types/server/meta.d.ts +2 -2
  213. package/dist/esm-types/server/meta.d.ts.map +1 -1
  214. package/dist/esm-types/values/compare.d.ts.map +1 -1
  215. package/dist/esm-types/values/index.d.ts +2 -2
  216. package/dist/esm-types/values/index.d.ts.map +1 -1
  217. package/dist/esm-types/values/size.d.ts +1 -1
  218. package/dist/esm-types/values/size.d.ts.map +1 -1
  219. package/dist/esm-types/values/validator.d.ts +10 -1
  220. package/dist/esm-types/values/validator.d.ts.map +1 -1
  221. package/dist/esm-types/values/validators.d.ts +14 -3
  222. package/dist/esm-types/values/validators.d.ts.map +1 -1
  223. package/dist/esm-types/values/value.d.ts +18 -1
  224. package/dist/esm-types/values/value.d.ts.map +1 -1
  225. package/dist/react.bundle.js +53 -11
  226. package/dist/react.bundle.js.map +2 -2
  227. package/package.json +8 -8
  228. package/schemas/convex.schema.json +2 -1
  229. package/src/browser/index.ts +2 -0
  230. package/src/browser/sync/client.test.ts +8 -1
  231. package/src/browser/sync/client.ts +63 -0
  232. package/src/browser/sync/paginated_query_client.test.ts +209 -1
  233. package/src/browser/sync/paginated_query_client.ts +29 -11
  234. package/src/cli/codegen_templates/component_api.ts +8 -3
  235. package/src/cli/codegen_templates/validator_helpers.test.ts +31 -0
  236. package/src/cli/codegen_templates/validator_helpers.ts +6 -0
  237. package/src/cli/deployment.ts +4 -1
  238. package/src/cli/deploymentCreate.test.ts +2 -2
  239. package/src/cli/deploymentCreate.ts +2 -2
  240. package/src/cli/generatedApi.ts +43 -43
  241. package/src/cli/lib/deployApi/validator.ts +1 -0
  242. package/src/cli/lib/deploymentSelection.ts +2 -2
  243. package/src/cli/lib/generateDocs.test.ts +1 -1
  244. package/src/cli/lib/generateDocs.ts +3 -3
  245. package/src/cli/lib/generatedFunctionLogsApi.ts +45 -45
  246. package/src/cli/lib/localDeployment/projectMismatch.ts +2 -2
  247. package/src/cli/lib/login.ts +33 -30
  248. package/src/cli/lib/typecheck.test.ts +71 -0
  249. package/src/cli/lib/typecheck.ts +33 -27
  250. package/src/cli/lib/usageLimits.ts +242 -0
  251. package/src/cli/usageLimits.ts +420 -0
  252. package/src/index.ts +1 -1
  253. package/src/react/ConvexAuthState.test.tsx +24 -8
  254. package/src/react/auth_websocket.test.tsx +39 -29
  255. package/src/react/client.ts +3 -2
  256. package/src/react/client_provided_base_client.test.tsx +154 -36
  257. package/src/server/audit_logging.test.ts +2 -0
  258. package/src/server/components/index.test.ts +33 -1
  259. package/src/server/components/index.ts +41 -14
  260. package/src/server/cron.test.ts +58 -0
  261. package/src/server/cron.ts +82 -21
  262. package/src/server/database.ts +17 -0
  263. package/src/server/impl/database_impl.ts +4 -1
  264. package/src/server/log.ts +4 -1
  265. package/src/server/logVars.ts +17 -1
  266. package/src/server/meta.ts +2 -2
  267. package/src/server/schema.test.ts +6 -1
  268. package/src/values/compare.ts +5 -2
  269. package/src/values/index.ts +2 -1
  270. package/src/values/size.test.ts +5 -0
  271. package/src/values/size.ts +5 -1
  272. package/src/values/validator.test.ts +16 -1
  273. package/src/values/validator.ts +13 -0
  274. package/src/values/validators.ts +29 -1
  275. package/src/values/value.test.ts +35 -0
  276. package/src/values/value.ts +56 -1
@@ -0,0 +1,420 @@
1
+ import { Command, Option } from "@commander-js/extra-typings";
2
+ import { Context } from "../bundler/context.js";
3
+ import { logFinishedStep, logMessage, logOutput } from "../bundler/log.js";
4
+ import { DeploymentSelectionOptions } from "./lib/api.js";
5
+ import { actionDescription } from "./lib/command.js";
6
+ import { ensureHasConvexDependency } from "./lib/utils/utils.js";
7
+ import { withRunningBackend } from "./lib/localDeployment/run.js";
8
+ import { selectEnvDeployment } from "./env.js";
9
+ import {
10
+ compareMetricNames,
11
+ createUsageLimit,
12
+ deleteUsageLimit,
13
+ findUsageLimitByKey,
14
+ getCurrentUsage,
15
+ listUsageLimits,
16
+ listUsageLimitsWithStatus,
17
+ metricLabel,
18
+ updateUsageLimit,
19
+ USAGE_LIMIT_METRICS,
20
+ USAGE_LIMIT_TYPES,
21
+ USAGE_LIMIT_WINDOWS,
22
+ UsageLimitStatus,
23
+ } from "./lib/usageLimits.js";
24
+
25
+ async function parseLimit(ctx: Context, value: string): Promise<number> {
26
+ const limit = Number(value);
27
+ if (!Number.isInteger(limit) || limit < 1) {
28
+ return ctx.crash({
29
+ exitCode: 1,
30
+ errorType: "fatal",
31
+ printedMessage: `error: --limit must be a positive integer, got "${value}".`,
32
+ });
33
+ }
34
+ return limit;
35
+ }
36
+
37
+ function formatTable(
38
+ header: string[],
39
+ rows: string[][],
40
+ rightAlign: number[] = [],
41
+ ): string {
42
+ const right = new Set(rightAlign);
43
+ const widths = header.map((cell, i) =>
44
+ Math.max(cell.length, ...rows.map((row) => (row[i] ?? "").length)),
45
+ );
46
+ const pad = (cell: string, i: number) =>
47
+ right.has(i) ? cell.padStart(widths[i]) : cell.padEnd(widths[i]);
48
+ const rule = (left: string, mid: string, r: string) =>
49
+ left + widths.map((w) => "\u2500".repeat(w + 2)).join(mid) + r;
50
+ const line = (cells: string[]) =>
51
+ "\u2502 " +
52
+ cells.map((cell, i) => pad(cell ?? "", i)).join(" \u2502 ") +
53
+ " \u2502";
54
+ return [
55
+ rule("\u250c", "\u252c", "\u2510"),
56
+ line(header),
57
+ rule("\u251c", "\u253c", "\u2524"),
58
+ ...rows.map(line),
59
+ rule("\u2514", "\u2534", "\u2518"),
60
+ ].join("\n");
61
+ }
62
+
63
+ const NUMBER_FORMAT_COMPACT = new Intl.NumberFormat("en-US", {
64
+ notation: "compact",
65
+ compactDisplay: "short",
66
+ maximumFractionDigits: 3,
67
+ });
68
+ function formatNumberCompact(value: number): string {
69
+ const formatted = NUMBER_FORMAT_COMPACT.format(value);
70
+ return formatted === "-0" ? "0" : formatted;
71
+ }
72
+
73
+ // Exact, thousands-separated amount for prose (success messages), where a
74
+ // precise value reads clearer than the compact table form.
75
+ const LIMIT_FORMAT = new Intl.NumberFormat("en-US");
76
+ function formatLimitAmount(value: number): string {
77
+ return LIMIT_FORMAT.format(value);
78
+ }
79
+
80
+ const NUMBER_FORMAT = new Intl.NumberFormat("en-US");
81
+ function formatNumber(value: number): string {
82
+ const formatted = NUMBER_FORMAT.format(value);
83
+ return formatted === "-0" ? "0" : formatted;
84
+ }
85
+
86
+ function unitFor(amount: number, unit: string): string {
87
+ return amount === 1 && unit === "calls" ? "call" : unit;
88
+ }
89
+
90
+ function formatAmount(value: number, unit: string | null): string {
91
+ return unit === null
92
+ ? formatNumberCompact(value)
93
+ : `${formatNumberCompact(value)} ${unitFor(value, unit)}`;
94
+ }
95
+
96
+ function usageLimitRow(limit: UsageLimitStatus): string[] {
97
+ const currentUsage =
98
+ limit.currentUsage === null
99
+ ? "\u2014"
100
+ : `${formatAmount(limit.currentUsage, limit.unit)} (${formatNumber(
101
+ Math.round((limit.currentUsage / limit.limit) * 100),
102
+ )}%)`;
103
+ return [
104
+ metricLabel(limit.metric),
105
+ limit.window,
106
+ limit.limitType,
107
+ formatAmount(limit.limit, limit.unit),
108
+ currentUsage,
109
+ limit.enabled ? "yes" : "no",
110
+ limit.triggered ? "yes" : "no",
111
+ ];
112
+ }
113
+
114
+ // The (metric, window, type) triple uniquely identifies a usage limit. `set`
115
+ // requires all three plus a limit; `update`/`remove` reuse them to locate an
116
+ // existing limit without needing its opaque id.
117
+ const metricOption = new Option(
118
+ "--metric <metric>",
119
+ "The metric to limit.",
120
+ ).choices(USAGE_LIMIT_METRICS);
121
+ const windowOption = new Option(
122
+ "--window <window>",
123
+ "The window the limit is measured over.",
124
+ ).choices(USAGE_LIMIT_WINDOWS);
125
+ const typeOption = new Option(
126
+ "--type <type>",
127
+ "`warning` only notifies; `disable` pauses the deployment when exceeded.",
128
+ ).choices(USAGE_LIMIT_TYPES);
129
+
130
+ function seedStatusMessage(
131
+ seedStatus: "pending" | "partial" | "failed",
132
+ ): string {
133
+ return seedStatus === "failed"
134
+ ? "We couldn't load this deployment's historical usage, so the usage shown below may understate its actual usage. Limits are still enforced going forward."
135
+ : "Historical usage is still being loaded, so the usage shown below may understate this deployment's actual usage. Check back shortly for accurate totals.";
136
+ }
137
+
138
+ const listCmd = new Command("list")
139
+ .summary("List configured usage limits")
140
+ .description(
141
+ [
142
+ "List the usage limits configured on your deployment.",
143
+ "",
144
+ "• List all usage limits: `npx convex deployment usage-limits list`",
145
+ "• Print as JSON: `npx convex deployment usage-limits list --json`",
146
+ ].join("\n"),
147
+ )
148
+ .option("--json", "Output the usage limits as JSON.")
149
+ .configureHelp({ showGlobalOptions: true })
150
+ .allowExcessArguments(false)
151
+ .action(async (cmdOptions, cmd) => {
152
+ const options = cmd.optsWithGlobals() as DeploymentSelectionOptions & {
153
+ json?: boolean;
154
+ };
155
+ const { ctx, deployment } = await selectEnvDeployment(options);
156
+ await ensureHasConvexDependency(ctx, "deployment usage-limits list");
157
+ await withRunningBackend({
158
+ ctx,
159
+ deployment,
160
+ action: async () => {
161
+ const { limits, seedStatus } = await listUsageLimitsWithStatus(
162
+ ctx,
163
+ deployment,
164
+ );
165
+ if (cmdOptions.json) {
166
+ logOutput(JSON.stringify(limits, null, 2));
167
+ return;
168
+ }
169
+ if (limits.length === 0) {
170
+ logMessage(
171
+ `No usage limits configured${deployment.deploymentNotice}.`,
172
+ );
173
+ return;
174
+ }
175
+ // "Triggered" is derived from reported usage, which a non-`complete`
176
+ // backfill may understate.
177
+ if (seedStatus !== "complete") {
178
+ logMessage(seedStatusMessage(seedStatus));
179
+ }
180
+ logOutput(
181
+ formatTable(
182
+ [
183
+ "Metric",
184
+ "Window",
185
+ "Type",
186
+ "Limit",
187
+ "Current Usage",
188
+ "Active",
189
+ "Triggered",
190
+ ],
191
+ limits.map(usageLimitRow),
192
+ [3, 4],
193
+ ),
194
+ );
195
+ },
196
+ });
197
+ });
198
+
199
+ const setCmd = new Command("set")
200
+ .summary("Create or update a usage limit")
201
+ .description(
202
+ [
203
+ "Create a usage limit, or update the existing one for the same",
204
+ "(metric, window, type). At most one limit exists per combination.",
205
+ "",
206
+ "• Set the amount (creates or replaces it):",
207
+ " `npx convex deployment usage-limits set --metric functionCalls --window day --type disable --limit 1000000`",
208
+ "• Deactivate without deleting: add `--inactive` (use `--active` to re-enable).",
209
+ "• Toggle active state without changing the amount: omit `--limit`.",
210
+ ].join("\n"),
211
+ )
212
+ .addOption(metricOption.makeOptionMandatory())
213
+ .addOption(windowOption.makeOptionMandatory())
214
+ .addOption(typeOption.makeOptionMandatory())
215
+ .option(
216
+ "--limit <limit>",
217
+ "The limit amount, in the metric's native units. Required when creating; kept as-is when omitted while updating.",
218
+ )
219
+ .option("--active", "Enforce the limit (the default for a new limit).")
220
+ .option("--inactive", "Create or leave the limit unenforced.")
221
+ .configureHelp({ showGlobalOptions: true })
222
+ .allowExcessArguments(false)
223
+ .action(async (cmdOptions, cmd) => {
224
+ const options = cmd.optsWithGlobals() as DeploymentSelectionOptions;
225
+ const { ctx, deployment } = await selectEnvDeployment(options);
226
+ await ensureHasConvexDependency(ctx, "deployment usage-limits set");
227
+ if (cmdOptions.active && cmdOptions.inactive) {
228
+ return ctx.crash({
229
+ exitCode: 1,
230
+ errorType: "fatal",
231
+ printedMessage: "error: Pass at most one of --active and --inactive.",
232
+ });
233
+ }
234
+ const newLimit =
235
+ cmdOptions.limit === undefined
236
+ ? undefined
237
+ : await parseLimit(ctx, cmdOptions.limit);
238
+ await withRunningBackend({
239
+ ctx,
240
+ deployment,
241
+ action: async () => {
242
+ const existing = (await listUsageLimits(ctx, deployment)).find(
243
+ (l) =>
244
+ l.metric === cmdOptions.metric &&
245
+ l.window === cmdOptions.window &&
246
+ l.limitType === cmdOptions.type,
247
+ );
248
+ const label = `${cmdOptions.type} usage limit on ${metricLabel(cmdOptions.metric)} per ${cmdOptions.window}`;
249
+
250
+ // No existing limit: create one. A limit amount is required.
251
+ if (existing === undefined) {
252
+ if (newLimit === undefined) {
253
+ return ctx.crash({
254
+ exitCode: 1,
255
+ errorType: "fatal",
256
+ printedMessage:
257
+ "error: --limit is required when creating a usage limit.",
258
+ });
259
+ }
260
+ const enabled = !cmdOptions.inactive;
261
+ const created = await createUsageLimit(ctx, deployment, {
262
+ metric: cmdOptions.metric,
263
+ window: cmdOptions.window,
264
+ limitType: cmdOptions.type,
265
+ limit: newLimit,
266
+ enabled,
267
+ });
268
+ logFinishedStep(
269
+ `Created ${label}: ${formatLimitAmount(created.limit)}, ${created.enabled ? "active" : "inactive"}${deployment.deploymentNotice}.`,
270
+ );
271
+ return;
272
+ }
273
+
274
+ // Existing limit: update it, surfacing exactly which fields changed.
275
+ const enabled = cmdOptions.active
276
+ ? true
277
+ : cmdOptions.inactive
278
+ ? false
279
+ : existing.enabled;
280
+ const limit = newLimit ?? existing.limit;
281
+ const changes: string[] = [];
282
+ if (limit !== existing.limit) {
283
+ changes.push(
284
+ `limit ${formatLimitAmount(existing.limit)} \u2192 ${formatLimitAmount(limit)}`,
285
+ );
286
+ }
287
+ if (enabled !== existing.enabled) {
288
+ changes.push(
289
+ `${existing.enabled ? "active" : "inactive"} \u2192 ${enabled ? "active" : "inactive"}`,
290
+ );
291
+ }
292
+ if (changes.length === 0) {
293
+ logFinishedStep(
294
+ `No changes to ${label} (${formatLimitAmount(existing.limit)}, ${existing.enabled ? "active" : "inactive"})${deployment.deploymentNotice}.`,
295
+ );
296
+ return;
297
+ }
298
+ await updateUsageLimit(ctx, deployment, existing.id, {
299
+ metric: existing.metric,
300
+ window: existing.window,
301
+ limitType: existing.limitType,
302
+ limit,
303
+ enabled,
304
+ });
305
+ logFinishedStep(
306
+ `Updated ${label}: ${changes.join(", ")}${deployment.deploymentNotice}.`,
307
+ );
308
+ },
309
+ });
310
+ });
311
+
312
+ const removeCmd = new Command("remove")
313
+ .alias("rm")
314
+ .alias("delete")
315
+ .summary("Delete a usage limit")
316
+ .description(
317
+ [
318
+ "Delete a usage limit, identified by its (metric, window, type).",
319
+ "",
320
+ "• `npx convex deployment usage-limits remove --metric functionCalls --window day --type warning`",
321
+ ].join("\n"),
322
+ )
323
+ .addOption(metricOption.makeOptionMandatory())
324
+ .addOption(windowOption.makeOptionMandatory())
325
+ .addOption(typeOption.makeOptionMandatory())
326
+ .configureHelp({ showGlobalOptions: true })
327
+ .allowExcessArguments(false)
328
+ .action(async (cmdOptions, cmd) => {
329
+ const options = cmd.optsWithGlobals() as DeploymentSelectionOptions;
330
+ const { ctx, deployment } = await selectEnvDeployment(options);
331
+ await ensureHasConvexDependency(ctx, "deployment usage-limits remove");
332
+ await withRunningBackend({
333
+ ctx,
334
+ deployment,
335
+ action: async () => {
336
+ const existing = await findUsageLimitByKey(ctx, deployment, {
337
+ metric: cmdOptions.metric,
338
+ window: cmdOptions.window,
339
+ limitType: cmdOptions.type,
340
+ });
341
+ await deleteUsageLimit(ctx, deployment, existing.id);
342
+ logFinishedStep(
343
+ `Deleted ${existing.limitType} usage limit on ${metricLabel(existing.metric)} per ${existing.window}${deployment.deploymentNotice}.`,
344
+ );
345
+ },
346
+ });
347
+ });
348
+
349
+ export const usage = new Command("usage")
350
+ .summary("Show current usage for each metric")
351
+ .description(
352
+ [
353
+ "Show usage so far in the current day and calendar month for every metric.",
354
+ "",
355
+ "• Show current usage: `npx convex deployment usage`",
356
+ "• Print as JSON: `npx convex deployment usage --json`",
357
+ ].join("\n"),
358
+ )
359
+ .option("--json", "Output the usage as JSON.")
360
+ .configureHelp({ showGlobalOptions: true })
361
+ .allowExcessArguments(false)
362
+ .addDeploymentSelectionOptions(actionDescription("Show current usage for"))
363
+ .action(async (cmdOptions, cmd) => {
364
+ const options = cmd.optsWithGlobals() as DeploymentSelectionOptions & {
365
+ json?: boolean;
366
+ };
367
+ const { ctx, deployment } = await selectEnvDeployment(options);
368
+ await ensureHasConvexDependency(ctx, "deployment usage");
369
+ await withRunningBackend({
370
+ ctx,
371
+ deployment,
372
+ action: async () => {
373
+ const usage = await getCurrentUsage(ctx, deployment);
374
+ if (cmdOptions.json) {
375
+ logOutput(JSON.stringify(usage, null, 2));
376
+ return;
377
+ }
378
+ // A non-`complete` backfill means the numbers can understate reality.
379
+ if (usage.seedStatus !== "complete") {
380
+ logMessage(seedStatusMessage(usage.seedStatus));
381
+ }
382
+ logOutput(
383
+ formatTable(
384
+ ["Metric", "Day", "Month"],
385
+ Object.entries(usage.metrics)
386
+ .sort(([a], [b]) => compareMetricNames(a, b))
387
+ .map(([metric, m]) => [
388
+ metricLabel(metric),
389
+ formatAmount(m.usage.current_day, m.unit),
390
+ formatAmount(m.usage.current_month, m.unit),
391
+ ]),
392
+ ),
393
+ );
394
+ },
395
+ });
396
+ });
397
+
398
+ export const usageLimits = new Command("usage-limits")
399
+ .summary("List and configure deployment usage limits")
400
+ .description(
401
+ [
402
+ "List and configure usage limits on your deployment.",
403
+ "",
404
+ "A usage limit either warns or pauses your deployment when a metric",
405
+ "(function calls, database bandwidth, …) crosses a threshold within a",
406
+ "daily or monthly window. Each limit is identified by its",
407
+ "(metric, window, type).",
408
+ "",
409
+ "• List usage limits: `npx convex deployment usage-limits list`",
410
+ "• Create or update one: `npx convex deployment usage-limits set --metric functionCalls --window day --type disable --limit 1000000`",
411
+ "• Delete one: `npx convex deployment usage-limits remove --metric functionCalls --window day --type disable`",
412
+ ].join("\n"),
413
+ )
414
+ .addCommand(listCmd)
415
+ .addCommand(setCmd)
416
+ .addCommand(removeCmd)
417
+ .helpCommand(false)
418
+ .addDeploymentSelectionOptions(
419
+ actionDescription("List and configure usage limits on"),
420
+ );
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export const version = "1.42.2";
1
+ export const version = "1.43.0-alpha.0";
@@ -10,6 +10,10 @@ import {
10
10
  ConvexReactClient,
11
11
  useConvexAuth,
12
12
  } from "./index.js";
13
+ import {
14
+ BaseConvexClient,
15
+ BaseConvexClientInterface,
16
+ } from "../browser/index.js";
13
17
 
14
18
  vi.useFakeTimers();
15
19
 
@@ -18,6 +22,16 @@ const flushPromises = async () => {
18
22
  await act(() => new Promise((timers as any).setImmediate));
19
23
  };
20
24
 
25
+ // Test helpers reach into `BaseConvexClient`'s private internals; the tests
26
+ // only ever construct `ConvexReactClient` without a `baseClient` override, so
27
+ // `sync` is always a real `BaseConvexClient` under the hood.
28
+ const syncAsBaseConvexClient = (sync: BaseConvexClientInterface) => {
29
+ if (!(sync instanceof BaseConvexClient)) {
30
+ throw new Error("Expected `sync` to be a `BaseConvexClient`");
31
+ }
32
+ return sync;
33
+ };
34
+
21
35
  test("setAuth legacy signature typechecks and doesn't throw", async () => {
22
36
  const convex = new ConvexReactClient("https://127.0.0.1:3001");
23
37
  // We're moving towards removing the Promise, but for backwards compatibility
@@ -201,8 +215,10 @@ function mockServerConfirmsAuth(
201
215
  clientClockSkew?: number,
202
216
  ) {
203
217
  act(() => {
204
- const querySetVersion = client.sync["remoteQuerySet"]["version"];
205
- client.sync["authenticationManager"].onTransition({
218
+ const querySetVersion = syncAsBaseConvexClient(client.sync)[
219
+ "remoteQuerySet"
220
+ ]["version"];
221
+ syncAsBaseConvexClient(client.sync)["authenticationManager"].onTransition({
206
222
  type: "Transition",
207
223
  startVersion: {
208
224
  ...querySetVersion,
@@ -311,7 +327,7 @@ test("isRefreshing reflects reauthentication in React tree", async () => {
311
327
 
312
328
  // Server rejects a token mid-session, triggering reauthentication.
313
329
  act(() => {
314
- convex.sync["authenticationManager"].onAuthError({
330
+ syncAsBaseConvexClient(convex.sync)["authenticationManager"].onAuthError({
315
331
  type: "AuthError",
316
332
  error: "token expired",
317
333
  baseVersion: 2,
@@ -362,7 +378,7 @@ test("isRefreshing becomes false when reauth fetch fails", async () => {
362
378
 
363
379
  // Server rejects the token; tryToReauthenticate runs but the fetcher
364
380
  // returns null on its retry.
365
- client.sync["authenticationManager"].onAuthError({
381
+ syncAsBaseConvexClient(client.sync)["authenticationManager"].onAuthError({
366
382
  type: "AuthError",
367
383
  error: "token expired",
368
384
  baseVersion: 1,
@@ -435,7 +451,7 @@ test("isFromOutdatedConfig during reauth resets isRefreshing via resetAuthState"
435
451
  onRefreshChange.mockClear();
436
452
 
437
453
  // Server rejects the token, kicking off tryToReauthenticate.
438
- client.sync["authenticationManager"].onAuthError({
454
+ syncAsBaseConvexClient(client.sync)["authenticationManager"].onAuthError({
439
455
  type: "AuthError",
440
456
  error: "token expired",
441
457
  baseVersion: 1,
@@ -482,7 +498,7 @@ test("isRefreshing is true during reauthentication after auth error", async () =
482
498
 
483
499
  // Simulate auth error from server
484
500
  act(() => {
485
- client.sync["authenticationManager"].onAuthError({
501
+ syncAsBaseConvexClient(client.sync)["authenticationManager"].onAuthError({
486
502
  type: "AuthError",
487
503
  error: "token expired",
488
504
  baseVersion: 1,
@@ -527,7 +543,7 @@ test("stop() during reauth notifies isRefreshing false", async () => {
527
543
  onRefreshChange.mockClear();
528
544
 
529
545
  // Server rejects the token so isRefreshing becomes true.
530
- client.sync["authenticationManager"].onAuthError({
546
+ syncAsBaseConvexClient(client.sync)["authenticationManager"].onAuthError({
531
547
  type: "AuthError",
532
548
  error: "token expired",
533
549
  baseVersion: 1,
@@ -564,7 +580,7 @@ test("initialAuthTokenReuse: sends only one Authenticate", async () => {
564
580
  );
565
581
 
566
582
  const sendMessageSpy = vi.spyOn(
567
- client.sync["webSocketManager"],
583
+ syncAsBaseConvexClient(client.sync)["webSocketManager"],
568
584
  "sendMessage",
569
585
  );
570
586