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
@@ -8,6 +8,7 @@ import {
8
8
  withInMemoryWebSocket,
9
9
  } from "../browser/sync/client_node_test_helpers.js";
10
10
  import { ConvexReactClient, ConvexReactClientOptions } from "./index.js";
11
+ import { BaseConvexClient } from "../browser/index.js";
11
12
  import waitForExpect from "wait-for-expect";
12
13
  import { anyApi } from "../server/index.js";
13
14
  import { Long } from "../vendor/long.js";
@@ -17,12 +18,21 @@ import {
17
18
  WireServerMessage,
18
19
  } from "../browser/sync/protocol.js";
19
20
 
20
- const testReactClient = (address: string, options?: ConvexReactClientOptions) =>
21
- new ConvexReactClient(address, {
21
+ const testReactClient = (
22
+ address: string,
23
+ options?: ConvexReactClientOptions,
24
+ ) => {
25
+ const client = new ConvexReactClient(address, {
22
26
  webSocketConstructor: nodeWebSocket,
23
27
  unsavedChangesWarning: false,
24
28
  ...options,
25
29
  });
30
+ const baseClient = client.sync;
31
+ if (!(baseClient instanceof BaseConvexClient)) {
32
+ throw new Error("Expected `sync` to be a `BaseConvexClient`");
33
+ }
34
+ return { client, baseClient };
35
+ };
26
36
 
27
37
  // Disabled due to flakes in CI
28
38
  // https://linear.app/convex/issue/ENG-7052/re-enable-auth-websocket-client-tests
@@ -33,7 +43,7 @@ describe.sequential.skip("auth websocket tests", () => {
33
43
  // with a constant token provider.
34
44
  test("Authenticate via valid static token", async () => {
35
45
  await withInMemoryWebSocket(async ({ address, receive, send }) => {
36
- const client = testReactClient(address);
46
+ const { client, baseClient } = testReactClient(address);
37
47
 
38
48
  const tokenFetcher = vi.fn(async () =>
39
49
  jwtEncode({ iat: 1234500, exp: 1244500 }, "secret"),
@@ -45,7 +55,7 @@ describe.sequential.skip("auth websocket tests", () => {
45
55
  expect((await receive()).type).toEqual("Authenticate");
46
56
  expect((await receive()).type).toEqual("ModifyQuerySet");
47
57
 
48
- const querySetVersion = client.sync["remoteQuerySet"]["version"];
58
+ const querySetVersion = baseClient["remoteQuerySet"]["version"];
49
59
 
50
60
  send({
51
61
  type: "Transition",
@@ -73,7 +83,7 @@ describe.sequential.skip("auth websocket tests", () => {
73
83
  // This happens when a user opens a page after their cached token expired
74
84
  test("Reauthenticate after token expiration with versioning", async () => {
75
85
  await withInMemoryWebSocket(async ({ address, receive, send, close }) => {
76
- const client = testReactClient(address);
86
+ const { client, baseClient } = testReactClient(address);
77
87
 
78
88
  let token = jwtEncode({ iat: 1234500, exp: 1244500 }, "wobabloobla");
79
89
  const fetchToken = async () => token;
@@ -106,7 +116,7 @@ describe.sequential.skip("auth websocket tests", () => {
106
116
  token: token,
107
117
  });
108
118
 
109
- const querySetVersion = client.sync["remoteQuerySet"]["version"];
119
+ const querySetVersion = baseClient["remoteQuerySet"]["version"];
110
120
 
111
121
  // Server accepts new token
112
122
  send({
@@ -138,7 +148,7 @@ describe.sequential.skip("auth websocket tests", () => {
138
148
  // fetch from a cache (say due to Prevent Cross Site tracking)
139
149
  test("Reauthenticate after token cache failure", async () => {
140
150
  await withInMemoryWebSocket(async ({ address, receive, send }) => {
141
- const client = testReactClient(address);
151
+ const { client, baseClient } = testReactClient(address);
142
152
 
143
153
  const freshToken = jwtEncode({ iat: 1234500, exp: 1244500 }, "secret");
144
154
  const tokenFetcher = vi.fn(
@@ -158,7 +168,7 @@ describe.sequential.skip("auth websocket tests", () => {
158
168
  token: freshToken,
159
169
  });
160
170
 
161
- const querySetVersion = client.sync["remoteQuerySet"]["version"];
171
+ const querySetVersion = baseClient["remoteQuerySet"]["version"];
162
172
 
163
173
  // Server accepts new token
164
174
  send({
@@ -189,7 +199,7 @@ describe.sequential.skip("auth websocket tests", () => {
189
199
  // This is usually a misconfigured server rejecting any token
190
200
  test("Fail when tokens are always rejected", async () => {
191
201
  await withInMemoryWebSocket(async ({ address, receive, send, close }) => {
192
- const client = testReactClient(address);
202
+ const { client } = testReactClient(address);
193
203
 
194
204
  const consoleSpy = vi
195
205
  .spyOn(global.console, "error")
@@ -287,7 +297,7 @@ describe.sequential.skip("auth websocket tests", () => {
287
297
  // it's authed but it cannot fetch a token at all.
288
298
  test("Fail when tokens cannot be fetched", async () => {
289
299
  await withInMemoryWebSocket(async ({ address, receive }) => {
290
- const client = testReactClient(address);
300
+ const { client } = testReactClient(address);
291
301
  const tokenFetcher = vi.fn(async () => null);
292
302
  const onAuthChange = vi.fn();
293
303
  void client.setAuth(tokenFetcher, onAuthChange);
@@ -307,7 +317,7 @@ describe.sequential.skip("auth websocket tests", () => {
307
317
 
308
318
  test("Client is protected against token rejection race", async () => {
309
319
  await withInMemoryWebSocket(async ({ address, receive, send, close }) => {
310
- const client = testReactClient(address);
320
+ const { client, baseClient } = testReactClient(address);
311
321
 
312
322
  const badToken = jwtEncode({ iat: 1234500, exp: 1244500 }, "wobalooba");
313
323
  const badTokenFetcher = vi.fn(async () => badToken);
@@ -319,7 +329,7 @@ describe.sequential.skip("auth websocket tests", () => {
319
329
  token: badToken,
320
330
  });
321
331
 
322
- const querySetVersion = client.sync["remoteQuerySet"]["version"];
332
+ const querySetVersion = baseClient["remoteQuerySet"]["version"];
323
333
 
324
334
  const goodToken = jwtEncode({ iat: 1234500, exp: 1244500 }, "secret");
325
335
  const goodTokenFetcher = vi.fn(async () => goodToken);
@@ -379,7 +389,7 @@ describe.sequential.skip("auth websocket tests", () => {
379
389
  // comes back while the client is waiting for server validation of the new token.
380
390
  test("Client ignores non-auth responses for token validation", async () => {
381
391
  await withInMemoryWebSocket(async ({ address, receive, send, close }) => {
382
- const client = testReactClient(address);
392
+ const { client, baseClient } = testReactClient(address);
383
393
  const ts = Math.ceil(Date.now() / 1000);
384
394
  const initialToken = jwtEncode({ iat: ts, exp: ts + 1000 }, "token1");
385
395
  const freshToken = jwtEncode({ iat: ts, exp: ts + 1000 }, "token2");
@@ -398,7 +408,7 @@ describe.sequential.skip("auth websocket tests", () => {
398
408
  token: initialToken,
399
409
  });
400
410
 
401
- const querySetVersion = client.sync["remoteQuerySet"]["version"];
411
+ const querySetVersion = baseClient["remoteQuerySet"]["version"];
402
412
 
403
413
  send({
404
414
  type: "Transition",
@@ -445,7 +455,7 @@ describe.sequential.skip("auth websocket tests", () => {
445
455
  token: freshToken,
446
456
  });
447
457
 
448
- const querySetVersion2 = client.sync["remoteQuerySet"]["version"];
458
+ const querySetVersion2 = baseClient["remoteQuerySet"]["version"];
449
459
 
450
460
  send({
451
461
  type: "Transition",
@@ -478,7 +488,7 @@ describe.sequential.skip("auth websocket tests", () => {
478
488
  test("Client maintains connection when refetch occurs during reauth attempt", async () => {
479
489
  await withInMemoryWebSocket(async ({ address, receive, send, close }) => {
480
490
  vi.useFakeTimers();
481
- const client = testReactClient(address);
491
+ const { client, baseClient } = testReactClient(address);
482
492
  // In this test we're going to simulate `fetchToken` taking a long time (1s)
483
493
  // so we can test race conditions, so the issued time will be 1s after the
484
494
  // fetched time.
@@ -543,7 +553,7 @@ describe.sequential.skip("auth websocket tests", () => {
543
553
  baseVersion: 0,
544
554
  token: initialToken,
545
555
  });
546
- const querySetVersion = client.sync["remoteQuerySet"]["version"];
556
+ const querySetVersion = baseClient["remoteQuerySet"]["version"];
547
557
 
548
558
  send({
549
559
  type: "Transition",
@@ -659,7 +669,7 @@ describe.sequential.skip("auth websocket tests", () => {
659
669
  // unauthenticated state.
660
670
  test("Client retries token validation on error", async () => {
661
671
  await withInMemoryWebSocket(async ({ address, receive, send, close }) => {
662
- const client = testReactClient(address);
672
+ const { client, baseClient } = testReactClient(address);
663
673
  const ts = Math.ceil(Date.now() / 1000);
664
674
  const token1 = jwtEncode({ iat: ts, exp: ts + 60 }, "token1");
665
675
  const token2 = jwtEncode({ iat: ts, exp: ts + 60 }, "token2");
@@ -674,7 +684,7 @@ describe.sequential.skip("auth websocket tests", () => {
674
684
  token: token1,
675
685
  });
676
686
 
677
- const querySetVersion = client.sync["remoteQuerySet"]["version"];
687
+ const querySetVersion = baseClient["remoteQuerySet"]["version"];
678
688
 
679
689
  send({
680
690
  type: "Transition",
@@ -751,7 +761,7 @@ describe.sequential.skip("auth websocket tests", () => {
751
761
 
752
762
  test("Authentication runs first", async () => {
753
763
  await withInMemoryWebSocket(async ({ address, receive, send }) => {
754
- const client = testReactClient(address);
764
+ const { client, baseClient } = testReactClient(address);
755
765
 
756
766
  const token1 = jwtEncode({ iat: 1234500, exp: 1244500 }, "secret");
757
767
  const tokenFetcher = vi.fn(async () => token1);
@@ -762,7 +772,7 @@ describe.sequential.skip("auth websocket tests", () => {
762
772
  token: token1,
763
773
  });
764
774
 
765
- const querySetVersion = client.sync["remoteQuerySet"]["version"];
775
+ const querySetVersion = baseClient["remoteQuerySet"]["version"];
766
776
 
767
777
  send({
768
778
  type: "Transition",
@@ -792,7 +802,7 @@ describe.sequential.skip("auth websocket tests", () => {
792
802
 
793
803
  test("Auth pause doesn't prevent unsubscribing from queries", async () => {
794
804
  await withInMemoryWebSocket(async ({ address, receive, send }) => {
795
- const client = testReactClient(address);
805
+ const { client, baseClient } = testReactClient(address);
796
806
 
797
807
  const token1 = jwtEncode({ iat: 1234500, exp: 1244500 }, "secret");
798
808
  const tokenFetcher = vi.fn(async () => token1);
@@ -807,7 +817,7 @@ describe.sequential.skip("auth websocket tests", () => {
807
817
  token: token1,
808
818
  });
809
819
 
810
- const querySetVersion = client.sync["remoteQuerySet"]["version"];
820
+ const querySetVersion = baseClient["remoteQuerySet"]["version"];
811
821
 
812
822
  send({
813
823
  type: "Transition",
@@ -829,7 +839,7 @@ describe.sequential.skip("auth websocket tests", () => {
829
839
  });
830
840
 
831
841
  await waitForExpect(() => {
832
- expect(client.sync["remoteQuerySet"]["version"].identity).toEqual(1);
842
+ expect(baseClient["remoteQuerySet"]["version"].identity).toEqual(1);
833
843
  });
834
844
 
835
845
  let resolve: (value: string) => void;
@@ -882,7 +892,7 @@ describe.sequential.skip("auth websocket tests", () => {
882
892
 
883
893
  test("Local state resume doesn't cause duplicate AddQuery", async () => {
884
894
  await withInMemoryWebSocket(async ({ address, receive }) => {
885
- const client = testReactClient(address);
895
+ const { client } = testReactClient(address);
886
896
 
887
897
  // First we subscribe
888
898
  client.watchQuery(anyApi.myQuery.default).onUpdate(() => {});
@@ -923,7 +933,7 @@ describe.sequential.skip("auth websocket tests", () => {
923
933
 
924
934
  test("Local state resume doesn't send both Add and Remove", async () => {
925
935
  await withInMemoryWebSocket(async ({ address, receive }) => {
926
- const client = testReactClient(address);
936
+ const { client } = testReactClient(address);
927
937
 
928
938
  // First we subscribe to kick off connect.
929
939
  client.watchQuery(anyApi.myQuery.default).onUpdate(() => {});
@@ -978,7 +988,7 @@ describe.sequential.skip("auth websocket tests", () => {
978
988
 
979
989
  test("Local state resume refcounts", async () => {
980
990
  await withInMemoryWebSocket(async ({ address, receive }) => {
981
- const client = testReactClient(address);
991
+ const { client } = testReactClient(address);
982
992
 
983
993
  // First we subscribe to kick off connect.
984
994
  client.watchQuery(anyApi.myQuery.default).onUpdate(() => {});
@@ -1034,7 +1044,7 @@ describe.sequential.skip("auth websocket tests", () => {
1034
1044
 
1035
1045
  test("Local state restart doesn't send both Add and Remove", async () => {
1036
1046
  await withInMemoryWebSocket(async ({ address, receive }) => {
1037
- const client = testReactClient(address);
1047
+ const { client } = testReactClient(address);
1038
1048
 
1039
1049
  // Set slow auth, causing pause while connecting
1040
1050
  let resolve: (value: string) => void;
@@ -1073,7 +1083,7 @@ describe.sequential.skip("auth websocket tests", () => {
1073
1083
  describe.sequential("authMode WebSocket", () => {
1074
1084
  test.each([false, true])("expectAuth: %s", async (expectAuth: boolean) => {
1075
1085
  await withInMemoryWebSocket(async ({ address, receive, close }) => {
1076
- const client = testReactClient(
1086
+ const { client } = testReactClient(
1077
1087
  address,
1078
1088
  expectAuth ? { expectAuth: true } : {},
1079
1089
  );
@@ -12,6 +12,7 @@ import {
12
12
  AuthTokenFetcher,
13
13
  BaseConvexClientOptions,
14
14
  ConnectionState,
15
+ BaseConvexClientInterface,
15
16
  } from "../browser/sync/client.js";
16
17
  import type { UserIdentityAttributes } from "../browser/sync/protocol.js";
17
18
  import { RequestForQueries, useQueries } from "./use_queries.js";
@@ -295,7 +296,7 @@ export interface ConvexReactClientOptions extends BaseConvexClientOptions {
295
296
  *
296
297
  * @internal
297
298
  */
298
- baseClient?: BaseConvexClient;
299
+ baseClient?: BaseConvexClientInterface;
299
300
 
300
301
  // FIXME: Ideally we define `ConvexReactClientOptions` as
301
302
  // `type ConvexReactClientOptions = BaseConvexClientOptions | { baseClient: BaseConvexClient }`
@@ -313,7 +314,7 @@ export interface ConvexReactClientOptions extends BaseConvexClientOptions {
313
314
  */
314
315
  export class ConvexReactClient {
315
316
  private address: string;
316
- private cachedSync?: BaseConvexClient | undefined;
317
+ private cachedSync?: BaseConvexClientInterface | undefined;
317
318
  private cachedPaginatedQueryClient?: PaginatedQueryClient | undefined;
318
319
  private listeners: Map<QueryToken | PaginatedQueryToken, Set<() => void>>;
319
320
  private options: ConvexReactClientOptions;
@@ -4,53 +4,171 @@ import ws from "ws";
4
4
  import { ConvexReactClient } from "./client.js";
5
5
  import { anyApi } from "../server/api.js";
6
6
  import { BaseConvexClient } from "../browser/index.js";
7
+ import { BaseConvexClientInterface } from "../browser/sync/client.js";
7
8
 
8
9
  const address = "https://127.0.0.1:3001";
9
10
 
10
- // A minimal subclass of `BaseConvexClient` used to prove that a
11
- // caller-provided `BaseConvexClient` (of any subclass) can be plugged into a
12
- // `ConvexReactClient` via the `client` option.
13
- class MinimalBaseConvexClient extends BaseConvexClient {}
11
+ describe("Creating a ConvexReactClient with a custom base client", () => {
12
+ describe("with a `BaseConvexClient` subclass", () => {
13
+ // A minimal subclass of `BaseConvexClient` used to prove that a
14
+ // caller-provided `BaseConvexClient` (of any subclass) can be plugged into
15
+ // a `ConvexReactClient` via the `client` option.
16
+ class MinimalBaseConvexClient extends BaseConvexClient {}
14
17
 
15
- const testBaseConvexClient = () =>
16
- new MinimalBaseConvexClient(address, () => {}, {
17
- webSocketConstructor: ws as unknown as typeof WebSocket,
18
- });
18
+ const testBaseConvexClient = () =>
19
+ new MinimalBaseConvexClient(address, () => {}, {
20
+ webSocketConstructor: ws as unknown as typeof WebSocket,
21
+ });
19
22
 
20
- describe("ConvexReactClient with a provided BaseConvexClient", () => {
21
- test("uses the provided client as its `sync` client", () => {
22
- const baseClient = testBaseConvexClient();
23
- const client = new ConvexReactClient(address, { baseClient });
24
- expect(client.sync).toBe(baseClient);
25
- });
23
+ test("uses the provided client as its `sync` client", () => {
24
+ const baseClient = testBaseConvexClient();
25
+ const client = new ConvexReactClient(address, { baseClient });
26
+ expect(client.sync).toBe(baseClient);
27
+ });
28
+
29
+ test("applies setAdminAuth called before the client is lazily instantiated", () => {
30
+ const baseClient = testBaseConvexClient();
31
+ const setAdminAuthSpy = vi.spyOn(baseClient, "setAdminAuth");
32
+ const client = new ConvexReactClient(address, { baseClient });
33
+ client.setAdminAuth("admin-token");
34
+ expect(setAdminAuthSpy).not.toHaveBeenCalled();
35
+
36
+ // Accessing `sync` lazily instantiates the client, applying admin auth.
37
+ expect(client.sync).toBe(baseClient);
38
+ expect(setAdminAuthSpy).toHaveBeenCalledWith("admin-token", undefined);
39
+ });
26
40
 
27
- test("applies setAdminAuth called before the client is lazily instantiated", () => {
28
- const baseClient = testBaseConvexClient();
29
- const setAdminAuthSpy = vi.spyOn(baseClient, "setAdminAuth");
30
- const client = new ConvexReactClient(address, { baseClient });
31
- client.setAdminAuth("admin-token");
32
- expect(setAdminAuthSpy).not.toHaveBeenCalled();
41
+ test("queries and mutations work through the provided client", async () => {
42
+ const baseClient = testBaseConvexClient();
43
+ const client = new ConvexReactClient(address, { baseClient });
33
44
 
34
- // Accessing `sync` lazily instantiates the client, applying admin auth.
35
- expect(client.sync).toBe(baseClient);
36
- expect(setAdminAuthSpy).toHaveBeenCalledWith("admin-token", undefined);
45
+ void client.mutation(
46
+ anyApi.myMutation.default,
47
+ {},
48
+ {
49
+ optimisticUpdate: (localStore) => {
50
+ localStore.setQuery(anyApi.myQuery.default, {}, "queryResult");
51
+ },
52
+ },
53
+ );
54
+
55
+ const queryResult = client.query(anyApi.myQuery.default, {});
56
+ expect(await queryResult).toStrictEqual("queryResult");
57
+ });
37
58
  });
38
59
 
39
- test("queries and mutations work through the provided client", async () => {
40
- const baseClient = testBaseConvexClient();
41
- const client = new ConvexReactClient(address, { baseClient });
60
+ describe("with a from-scratch BaseConvexClientInterface implementation", () => {
61
+ // A from-scratch (non-subclass) implementation of `BaseConvexClientInterface`.
62
+ // Unlike `MinimalBaseConvexClient` above, this does not extend
63
+ // `BaseConvexClient`, so TypeScript forces every public member to be
64
+ // declared here explicitly — if `BaseConvexClientInterface` ever gains a member,
65
+ // this class fails to compile until a matching forwarding method is added.
66
+ class ComposedBaseConvexClient implements BaseConvexClientInterface {
67
+ private inner: BaseConvexClient;
68
+
69
+ constructor(...args: ConstructorParameters<typeof BaseConvexClient>) {
70
+ this.inner = new BaseConvexClient(...args);
71
+ }
72
+
73
+ addOnTransitionHandler(
74
+ ...args: Parameters<BaseConvexClient["addOnTransitionHandler"]>
75
+ ) {
76
+ return this.inner.addOnTransitionHandler(...args);
77
+ }
78
+ setAuth(...args: Parameters<BaseConvexClient["setAuth"]>) {
79
+ return this.inner.setAuth(...args);
80
+ }
81
+ setAdminAuth(...args: Parameters<BaseConvexClient["setAdminAuth"]>) {
82
+ return this.inner.setAdminAuth(...args);
83
+ }
84
+ clearAuth() {
85
+ return this.inner.clearAuth();
86
+ }
87
+ subscribe(...args: Parameters<BaseConvexClient["subscribe"]>) {
88
+ return this.inner.subscribe(...args);
89
+ }
90
+ localQueryResult(
91
+ ...args: Parameters<BaseConvexClient["localQueryResult"]>
92
+ ) {
93
+ return this.inner.localQueryResult(...args);
94
+ }
95
+ localQueryResultByToken(
96
+ ...args: Parameters<BaseConvexClient["localQueryResultByToken"]>
97
+ ) {
98
+ return this.inner.localQueryResultByToken(...args);
99
+ }
100
+ hasLocalQueryResultByToken(
101
+ ...args: Parameters<BaseConvexClient["hasLocalQueryResultByToken"]>
102
+ ) {
103
+ return this.inner.hasLocalQueryResultByToken(...args);
104
+ }
105
+ localQueryLogs(...args: Parameters<BaseConvexClient["localQueryLogs"]>) {
106
+ return this.inner.localQueryLogs(...args);
107
+ }
108
+ queryJournal(...args: Parameters<BaseConvexClient["queryJournal"]>) {
109
+ return this.inner.queryJournal(...args);
110
+ }
111
+ connectionState() {
112
+ return this.inner.connectionState();
113
+ }
114
+ subscribeToConnectionState(
115
+ ...args: Parameters<BaseConvexClient["subscribeToConnectionState"]>
116
+ ) {
117
+ return this.inner.subscribeToConnectionState(...args);
118
+ }
119
+ mutation(...args: Parameters<BaseConvexClient["mutation"]>) {
120
+ return this.inner.mutation(...args);
121
+ }
122
+ action(...args: Parameters<BaseConvexClient["action"]>) {
123
+ return this.inner.action(...args);
124
+ }
125
+ close() {
126
+ return this.inner.close();
127
+ }
128
+ get url() {
129
+ return this.inner.url;
130
+ }
131
+ }
132
+
133
+ const testBaseConvexClient = () =>
134
+ new ComposedBaseConvexClient(address, () => {}, {
135
+ webSocketConstructor: ws as unknown as typeof WebSocket,
136
+ });
137
+
138
+ test("uses the provided client as its `sync` client", () => {
139
+ const baseClient = testBaseConvexClient();
140
+ const client = new ConvexReactClient(address, { baseClient });
141
+ expect(client.sync).toBe(baseClient);
142
+ });
143
+
144
+ test("applies setAdminAuth called before the client is lazily instantiated", () => {
145
+ const baseClient = testBaseConvexClient();
146
+ const setAdminAuthSpy = vi.spyOn(baseClient, "setAdminAuth");
147
+ const client = new ConvexReactClient(address, { baseClient });
148
+ client.setAdminAuth("admin-token");
149
+ expect(setAdminAuthSpy).not.toHaveBeenCalled();
150
+
151
+ // Accessing `sync` lazily instantiates the client, applying admin auth.
152
+ expect(client.sync).toBe(baseClient);
153
+ expect(setAdminAuthSpy).toHaveBeenCalledWith("admin-token", undefined);
154
+ });
155
+
156
+ test("queries and mutations work through the provided client", async () => {
157
+ const baseClient = testBaseConvexClient();
158
+ const client = new ConvexReactClient(address, { baseClient });
42
159
 
43
- void client.mutation(
44
- anyApi.myMutation.default,
45
- {},
46
- {
47
- optimisticUpdate: (localStore) => {
48
- localStore.setQuery(anyApi.myQuery.default, {}, "queryResult");
160
+ void client.mutation(
161
+ anyApi.myMutation.default,
162
+ {},
163
+ {
164
+ optimisticUpdate: (localStore) => {
165
+ localStore.setQuery(anyApi.myQuery.default, {}, "queryResult");
166
+ },
49
167
  },
50
- },
51
- );
168
+ );
52
169
 
53
- const queryResult = client.query(anyApi.myQuery.default, {});
54
- expect(await queryResult).toStrictEqual("queryResult");
170
+ const queryResult = client.query(anyApi.myQuery.default, {});
171
+ expect(await queryResult).toStrictEqual("queryResult");
172
+ });
55
173
  });
56
174
  });
@@ -24,6 +24,7 @@ describe("cloneWithSentinels", () => {
24
24
  agent: log.vars.userAgent,
25
25
  ts: log.vars.now,
26
26
  reqId: log.vars.requestId,
27
+ admin: log.internalVars.convexActor,
27
28
  note: "hello",
28
29
  };
29
30
  const result = cloneWithSentinels(input);
@@ -33,6 +34,7 @@ describe("cloneWithSentinels", () => {
33
34
  agent: { $var: "userAgent" },
34
35
  ts: { $var: "now" },
35
36
  reqId: { $var: "requestId" },
37
+ admin: { $var: "convexActor" },
36
38
  note: "hello",
37
39
  });
38
40
  });
@@ -1,6 +1,7 @@
1
1
  import { expect, test } from "vitest";
2
2
 
3
- import { defineApp } from "./index.js";
3
+ import { v } from "../../values/index.js";
4
+ import { defineApp, defineComponent } from "./index.js";
4
5
 
5
6
  test("app.use throws on empty component name", () => {
6
7
  const app = defineApp() as any;
@@ -14,3 +15,34 @@ test("app.use throws on empty component name", () => {
14
15
  /component name cannot be empty/i,
15
16
  );
16
17
  });
18
+
19
+ test("app.use requires the options arg only when a component declares required env vars", () => {
20
+ const app = defineApp();
21
+ const requiredEnv = defineComponent("requiredEnv", {
22
+ env: { THING_I_NEED: v.string() },
23
+ });
24
+ const optionalEnv = defineComponent("optionalEnv", {
25
+ env: { THING_I_WANT: v.optional(v.string()) },
26
+ });
27
+ const emptyEnv = defineComponent("emptyEnv", { env: {} });
28
+
29
+ // Type-only assertions. Never invoked: `app.use` needs a runtime component
30
+ // with a `componentDefinitionPath`, which these plain definitions lack.
31
+ void (() => {
32
+ // @ts-expect-error a required env var means the options arg can't be dropped
33
+ app.use(requiredEnv);
34
+ // @ts-expect-error nor can `env` itself be omitted
35
+ app.use(requiredEnv, {});
36
+ app.use(requiredEnv, { env: { THING_I_NEED: "a string" } });
37
+
38
+ // With only optional env vars, both forms are allowed.
39
+ app.use(optionalEnv);
40
+ app.use(optionalEnv, { env: {} });
41
+ app.use(optionalEnv, { env: { THING_I_WANT: "a string" } });
42
+
43
+ // A component declaring no env vars needs no `env` (and `env: {}` is fine).
44
+ app.use(emptyEnv);
45
+ app.use(emptyEnv, {});
46
+ app.use(emptyEnv, { env: {} });
47
+ });
48
+ });
@@ -105,7 +105,7 @@ export type ComponentDefinition<
105
105
  */
106
106
  use<Definition extends ComponentDefinition<any, any>>(
107
107
  definition: Definition,
108
- options?: UseOptions<Definition>,
108
+ ...args: UseArgs<Definition>
109
109
  ): InstalledComponent<Definition>;
110
110
 
111
111
  /**
@@ -136,30 +136,55 @@ type ComponentDefinitionExports<T extends ComponentDefinition<any, any>> =
136
136
  type ComponentDefinitionEnv<T extends ComponentDefinition<any, any>> =
137
137
  T["__env"];
138
138
 
139
+ /**
140
+ * The names in an {@link EnvDefinition} whose validators are required (not
141
+ * wrapped in `v.optional(...)`).
142
+ */
143
+ type RequiredEnvKeys<E extends EnvDefinition> = {
144
+ [K in keyof E]: E[K] extends Validator<any, "optional", any> ? never : K;
145
+ }[keyof E];
146
+
139
147
  /**
140
148
  * Options for installing a component via `app.use()` or `component.use()`.
141
149
  *
142
- * If the component declares required env vars, the `env` property is required.
150
+ * If the component declares any required env vars, the `env` property is
151
+ * required. Otherwise it is optional, so that a component with no env vars (or
152
+ * only optional ones) can be installed without passing `env`.
143
153
  */
144
154
  type UseOptions<Definition extends ComponentDefinition<any, any>> =
145
- keyof ComponentDefinitionEnv<Definition> extends never
146
- ? { name?: string; httpPrefix?: string }
155
+ RequiredEnvKeys<ComponentDefinitionEnv<Definition>> extends never
156
+ ? {
157
+ name?: string;
158
+ httpPrefix?: string;
159
+ env?: UseOptionsEnv<ComponentDefinitionEnv<Definition>>;
160
+ }
147
161
  : {
148
162
  name?: string;
149
163
  httpPrefix?: string;
150
164
  env: UseOptionsEnv<ComponentDefinitionEnv<Definition>>;
151
165
  };
152
166
 
167
+ /**
168
+ * The arguments to `use()` after the component definition.
169
+ *
170
+ * When the component declares required env vars the `options` argument is
171
+ * mandatory (so it can't be dropped to skip the required `env`); otherwise it
172
+ * is optional.
173
+ */
174
+ type UseArgs<Definition extends ComponentDefinition<any, any>> =
175
+ RequiredEnvKeys<ComponentDefinitionEnv<Definition>> extends never
176
+ ? [options?: UseOptions<Definition>]
177
+ : [options: UseOptions<Definition>];
178
+
153
179
  type UseOptionsEnv<E extends EnvDefinition> = Expand<
154
180
  {
155
- [K in keyof E as E[K] extends VOptional<any> ? never : K]:
181
+ [K in keyof E as E[K] extends Validator<any, "optional", any> ? never : K]:
156
182
  | Infer<E[K]>
157
183
  | EnvRef;
158
184
  } & {
159
- [K in keyof E as E[K] extends VOptional<any> ? K : never]?:
160
- | Infer<E[K]>
161
- | EnvRef
162
- | undefined;
185
+ [K in keyof E as E[K] extends Validator<any, "optional", any>
186
+ ? K
187
+ : never]?: Infer<E[K]> | EnvRef | undefined;
163
188
  }
164
189
  >;
165
190
 
@@ -213,11 +238,13 @@ export type EnvDefinition = Record<
213
238
  */
214
239
  export type EnvFromDefinition<E extends EnvDefinition> = Expand<
215
240
  {
216
- [K in keyof E as E[K] extends VOptional<any> ? never : K]: Infer<E[K]>;
241
+ [K in keyof E as E[K] extends Validator<any, "optional", any>
242
+ ? never
243
+ : K]: Infer<E[K]>;
217
244
  } & {
218
- [K in keyof E as E[K] extends VOptional<any> ? K : never]?:
219
- | Infer<E[K]>
220
- | undefined;
245
+ [K in keyof E as E[K] extends Validator<any, "optional", any>
246
+ ? K
247
+ : never]?: Infer<E[K]> | undefined;
221
248
  }
222
249
  >;
223
250
 
@@ -269,7 +296,7 @@ export type AppDefinition<Env extends EnvDefinition = EnvDefinition> = {
269
296
  */
270
297
  use<Definition extends ComponentDefinition<any, any>>(
271
298
  definition: Definition,
272
- options?: UseOptions<Definition>,
299
+ ...args: UseArgs<Definition>
273
300
  ): InstalledComponent<Definition>;
274
301
 
275
302
  /**