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.
- package/CHANGELOG.md +8 -1
- package/bin/main-dev +6 -1
- package/dist/browser.bundle.js +49 -10
- package/dist/browser.bundle.js.map +2 -2
- package/dist/cjs/browser/index.js.map +2 -2
- package/dist/cjs/browser/simple_client-node.js +35 -35
- package/dist/cjs/browser/simple_client-node.js.map +2 -2
- package/dist/cjs/browser/sync/client.js.map +1 -1
- package/dist/cjs/browser/sync/paginated_query_client.js +20 -9
- package/dist/cjs/browser/sync/paginated_query_client.js.map +2 -2
- package/dist/cjs/cli/codegen_templates/component_api.js +2 -2
- package/dist/cjs/cli/codegen_templates/component_api.js.map +2 -2
- package/dist/cjs/cli/codegen_templates/validator_helpers.js +2 -0
- package/dist/cjs/cli/codegen_templates/validator_helpers.js.map +2 -2
- package/dist/cjs/cli/deployment.js +2 -1
- package/dist/cjs/cli/deployment.js.map +2 -2
- package/dist/cjs/cli/deploymentCreate.js.map +2 -2
- package/dist/cjs/cli/generatedApi.js.map +1 -1
- package/dist/cjs/cli/lib/deployApi/validator.js +1 -0
- package/dist/cjs/cli/lib/deployApi/validator.js.map +2 -2
- package/dist/cjs/cli/lib/deploymentSelection.js.map +2 -2
- package/dist/cjs/cli/lib/generateDocs.js +1 -1
- package/dist/cjs/cli/lib/generateDocs.js.map +2 -2
- package/dist/cjs/cli/lib/generatedFunctionLogsApi.js.map +1 -1
- package/dist/cjs/cli/lib/localDeployment/projectMismatch.js.map +2 -2
- package/dist/cjs/cli/lib/login.js +23 -23
- package/dist/cjs/cli/lib/login.js.map +2 -2
- package/dist/cjs/cli/lib/typecheck.js +28 -25
- package/dist/cjs/cli/lib/typecheck.js.map +2 -2
- package/dist/cjs/cli/lib/usageLimits.js +164 -0
- package/dist/cjs/cli/lib/usageLimits.js.map +7 -0
- package/dist/cjs/cli/usageLimits.js +326 -0
- package/dist/cjs/cli/usageLimits.js.map +7 -0
- package/dist/cjs/index.js +1 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/react/client.js.map +2 -2
- package/dist/cjs/server/components/index.js.map +2 -2
- package/dist/cjs/server/cron.js +33 -29
- package/dist/cjs/server/cron.js.map +2 -2
- package/dist/cjs/server/database.js.map +1 -1
- package/dist/cjs/server/impl/database_impl.js +3 -0
- package/dist/cjs/server/impl/database_impl.js.map +2 -2
- package/dist/cjs/server/log.js +2 -1
- package/dist/cjs/server/log.js.map +2 -2
- package/dist/cjs/server/logVars.js +12 -1
- package/dist/cjs/server/logVars.js.map +2 -2
- package/dist/cjs/server/meta.js.map +1 -1
- package/dist/cjs/values/compare.js +5 -1
- package/dist/cjs/values/compare.js.map +2 -2
- package/dist/cjs/values/index.js +1 -0
- package/dist/cjs/values/index.js.map +2 -2
- package/dist/cjs/values/size.js +4 -0
- package/dist/cjs/values/size.js.map +2 -2
- package/dist/cjs/values/validator.js +11 -0
- package/dist/cjs/values/validator.js.map +2 -2
- package/dist/cjs/values/validators.js +20 -0
- package/dist/cjs/values/validators.js.map +2 -2
- package/dist/cjs/values/value.js +31 -0
- package/dist/cjs/values/value.js.map +2 -2
- package/dist/cjs-types/browser/index.d.ts.map +1 -1
- package/dist/cjs-types/browser/sync/paginated_query_client.d.ts +1 -38
- package/dist/cjs-types/browser/sync/paginated_query_client.d.ts.map +1 -1
- package/dist/cjs-types/cli/codegen_templates/component_api.d.ts.map +1 -1
- package/dist/cjs-types/cli/codegen_templates/validator_helpers.d.ts.map +1 -1
- package/dist/cjs-types/cli/codegen_templates/validator_helpers.test.d.ts +2 -0
- package/dist/cjs-types/cli/codegen_templates/validator_helpers.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/deployment.d.ts.map +1 -1
- package/dist/cjs-types/cli/deploymentCreate.d.ts +2 -2
- package/dist/cjs-types/cli/generatedApi.d.ts +43 -43
- package/dist/cjs-types/cli/generatedApi.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/deployApi/validator.d.ts +6 -0
- package/dist/cjs-types/cli/lib/deployApi/validator.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/deploymentSelection.d.ts +2 -2
- package/dist/cjs-types/cli/lib/deploymentSelection.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/generatedFunctionLogsApi.d.ts +45 -45
- package/dist/cjs-types/cli/lib/generatedFunctionLogsApi.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/localDeployment/projectMismatch.d.ts +2 -2
- package/dist/cjs-types/cli/lib/localDeployment/projectMismatch.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/login.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/mcp/tools/insights.d.ts +24 -24
- package/dist/cjs-types/cli/lib/typecheck.d.ts +2 -0
- package/dist/cjs-types/cli/lib/typecheck.d.ts.map +1 -1
- package/dist/cjs-types/cli/lib/typecheck.test.d.ts +2 -0
- package/dist/cjs-types/cli/lib/typecheck.test.d.ts.map +1 -0
- package/dist/cjs-types/cli/lib/usageLimits.d.ts +46 -0
- package/dist/cjs-types/cli/lib/usageLimits.d.ts.map +1 -0
- package/dist/cjs-types/cli/usageLimits.d.ts +22 -0
- package/dist/cjs-types/cli/usageLimits.d.ts.map +1 -0
- package/dist/cjs-types/index.d.ts +1 -1
- package/dist/cjs-types/index.d.ts.map +1 -1
- package/dist/cjs-types/react/client.d.ts.map +1 -1
- package/dist/cjs-types/server/components/index.d.ts +26 -8
- package/dist/cjs-types/server/components/index.d.ts.map +1 -1
- package/dist/cjs-types/server/cron.d.ts +20 -11
- package/dist/cjs-types/server/cron.d.ts.map +1 -1
- package/dist/cjs-types/server/cron.test.d.ts +2 -0
- package/dist/cjs-types/server/cron.test.d.ts.map +1 -0
- package/dist/cjs-types/server/database.d.ts.map +1 -1
- package/dist/cjs-types/server/impl/database_impl.d.ts.map +1 -1
- package/dist/cjs-types/server/log.d.ts.map +1 -1
- package/dist/cjs-types/server/logVars.d.ts +14 -1
- package/dist/cjs-types/server/logVars.d.ts.map +1 -1
- package/dist/cjs-types/server/meta.d.ts +2 -2
- package/dist/cjs-types/server/meta.d.ts.map +1 -1
- package/dist/cjs-types/values/compare.d.ts.map +1 -1
- package/dist/cjs-types/values/index.d.ts +2 -2
- package/dist/cjs-types/values/index.d.ts.map +1 -1
- package/dist/cjs-types/values/size.d.ts +1 -1
- package/dist/cjs-types/values/size.d.ts.map +1 -1
- package/dist/cjs-types/values/validator.d.ts +10 -1
- package/dist/cjs-types/values/validator.d.ts.map +1 -1
- package/dist/cjs-types/values/validators.d.ts +14 -3
- package/dist/cjs-types/values/validators.d.ts.map +1 -1
- package/dist/cjs-types/values/value.d.ts +18 -1
- package/dist/cjs-types/values/value.d.ts.map +1 -1
- package/dist/cli.bundle.cjs +2327 -1866
- package/dist/cli.bundle.cjs.map +4 -4
- package/dist/esm/browser/index.js.map +2 -2
- package/dist/esm/browser/simple_client-node.js +35 -35
- package/dist/esm/browser/simple_client-node.js.map +1 -1
- package/dist/esm/browser/sync/client.js.map +1 -1
- package/dist/esm/browser/sync/paginated_query_client.js +20 -9
- package/dist/esm/browser/sync/paginated_query_client.js.map +2 -2
- package/dist/esm/cli/codegen_templates/component_api.js +8 -3
- package/dist/esm/cli/codegen_templates/component_api.js.map +2 -2
- package/dist/esm/cli/codegen_templates/validator_helpers.js +2 -0
- package/dist/esm/cli/codegen_templates/validator_helpers.js.map +2 -2
- package/dist/esm/cli/deployment.js +2 -1
- package/dist/esm/cli/deployment.js.map +2 -2
- package/dist/esm/cli/deploymentCreate.js.map +2 -2
- package/dist/esm/cli/lib/deployApi/validator.js +1 -0
- package/dist/esm/cli/lib/deployApi/validator.js.map +2 -2
- package/dist/esm/cli/lib/deploymentSelection.js.map +2 -2
- package/dist/esm/cli/lib/generateDocs.js +1 -1
- package/dist/esm/cli/lib/generateDocs.js.map +2 -2
- package/dist/esm/cli/lib/localDeployment/projectMismatch.js.map +2 -2
- package/dist/esm/cli/lib/login.js +23 -23
- package/dist/esm/cli/lib/login.js.map +2 -2
- package/dist/esm/cli/lib/typecheck.js +27 -25
- package/dist/esm/cli/lib/typecheck.js.map +2 -2
- package/dist/esm/cli/lib/usageLimits.js +129 -0
- package/dist/esm/cli/lib/usageLimits.js.map +7 -0
- package/dist/esm/cli/usageLimits.js +316 -0
- package/dist/esm/cli/usageLimits.js.map +7 -0
- package/dist/esm/index.js +1 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/react/client.js.map +2 -2
- package/dist/esm/server/components/index.js.map +2 -2
- package/dist/esm/server/cron.js +33 -29
- package/dist/esm/server/cron.js.map +2 -2
- package/dist/esm/server/impl/database_impl.js +4 -1
- package/dist/esm/server/impl/database_impl.js.map +2 -2
- package/dist/esm/server/log.js +3 -2
- package/dist/esm/server/log.js.map +2 -2
- package/dist/esm/server/logVars.js +11 -1
- package/dist/esm/server/logVars.js.map +2 -2
- package/dist/esm/values/compare.js +5 -1
- package/dist/esm/values/compare.js.map +2 -2
- package/dist/esm/values/index.js +1 -1
- package/dist/esm/values/index.js.map +2 -2
- package/dist/esm/values/size.js +4 -0
- package/dist/esm/values/size.js.map +2 -2
- package/dist/esm/values/validator.js +12 -0
- package/dist/esm/values/validator.js.map +2 -2
- package/dist/esm/values/validators.js +19 -0
- package/dist/esm/values/validators.js.map +2 -2
- package/dist/esm/values/value.js +29 -1
- package/dist/esm/values/value.js.map +2 -2
- package/dist/esm-types/browser/index.d.ts.map +1 -1
- package/dist/esm-types/browser/sync/paginated_query_client.d.ts +1 -38
- package/dist/esm-types/browser/sync/paginated_query_client.d.ts.map +1 -1
- package/dist/esm-types/cli/codegen_templates/component_api.d.ts.map +1 -1
- package/dist/esm-types/cli/codegen_templates/validator_helpers.d.ts.map +1 -1
- package/dist/esm-types/cli/codegen_templates/validator_helpers.test.d.ts +2 -0
- package/dist/esm-types/cli/codegen_templates/validator_helpers.test.d.ts.map +1 -0
- package/dist/esm-types/cli/deployment.d.ts.map +1 -1
- package/dist/esm-types/cli/deploymentCreate.d.ts +2 -2
- package/dist/esm-types/cli/generatedApi.d.ts +43 -43
- package/dist/esm-types/cli/generatedApi.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/deployApi/validator.d.ts +6 -0
- package/dist/esm-types/cli/lib/deployApi/validator.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/deploymentSelection.d.ts +2 -2
- package/dist/esm-types/cli/lib/deploymentSelection.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/generatedFunctionLogsApi.d.ts +45 -45
- package/dist/esm-types/cli/lib/generatedFunctionLogsApi.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/localDeployment/projectMismatch.d.ts +2 -2
- package/dist/esm-types/cli/lib/localDeployment/projectMismatch.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/login.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/mcp/tools/insights.d.ts +24 -24
- package/dist/esm-types/cli/lib/typecheck.d.ts +2 -0
- package/dist/esm-types/cli/lib/typecheck.d.ts.map +1 -1
- package/dist/esm-types/cli/lib/typecheck.test.d.ts +2 -0
- package/dist/esm-types/cli/lib/typecheck.test.d.ts.map +1 -0
- package/dist/esm-types/cli/lib/usageLimits.d.ts +46 -0
- package/dist/esm-types/cli/lib/usageLimits.d.ts.map +1 -0
- package/dist/esm-types/cli/usageLimits.d.ts +22 -0
- package/dist/esm-types/cli/usageLimits.d.ts.map +1 -0
- package/dist/esm-types/index.d.ts +1 -1
- package/dist/esm-types/index.d.ts.map +1 -1
- package/dist/esm-types/react/client.d.ts.map +1 -1
- package/dist/esm-types/server/components/index.d.ts +26 -8
- package/dist/esm-types/server/components/index.d.ts.map +1 -1
- package/dist/esm-types/server/cron.d.ts +20 -11
- package/dist/esm-types/server/cron.d.ts.map +1 -1
- package/dist/esm-types/server/cron.test.d.ts +2 -0
- package/dist/esm-types/server/cron.test.d.ts.map +1 -0
- package/dist/esm-types/server/database.d.ts.map +1 -1
- package/dist/esm-types/server/impl/database_impl.d.ts.map +1 -1
- package/dist/esm-types/server/log.d.ts.map +1 -1
- package/dist/esm-types/server/logVars.d.ts +14 -1
- package/dist/esm-types/server/logVars.d.ts.map +1 -1
- package/dist/esm-types/server/meta.d.ts +2 -2
- package/dist/esm-types/server/meta.d.ts.map +1 -1
- package/dist/esm-types/values/compare.d.ts.map +1 -1
- package/dist/esm-types/values/index.d.ts +2 -2
- package/dist/esm-types/values/index.d.ts.map +1 -1
- package/dist/esm-types/values/size.d.ts +1 -1
- package/dist/esm-types/values/size.d.ts.map +1 -1
- package/dist/esm-types/values/validator.d.ts +10 -1
- package/dist/esm-types/values/validator.d.ts.map +1 -1
- package/dist/esm-types/values/validators.d.ts +14 -3
- package/dist/esm-types/values/validators.d.ts.map +1 -1
- package/dist/esm-types/values/value.d.ts +18 -1
- package/dist/esm-types/values/value.d.ts.map +1 -1
- package/dist/react.bundle.js +53 -11
- package/dist/react.bundle.js.map +2 -2
- package/package.json +8 -8
- package/schemas/convex.schema.json +2 -1
- package/src/browser/index.ts +2 -0
- package/src/browser/sync/client.test.ts +8 -1
- package/src/browser/sync/client.ts +63 -0
- package/src/browser/sync/paginated_query_client.test.ts +209 -1
- package/src/browser/sync/paginated_query_client.ts +29 -11
- package/src/cli/codegen_templates/component_api.ts +8 -3
- package/src/cli/codegen_templates/validator_helpers.test.ts +31 -0
- package/src/cli/codegen_templates/validator_helpers.ts +6 -0
- package/src/cli/deployment.ts +4 -1
- package/src/cli/deploymentCreate.test.ts +2 -2
- package/src/cli/deploymentCreate.ts +2 -2
- package/src/cli/generatedApi.ts +43 -43
- package/src/cli/lib/deployApi/validator.ts +1 -0
- package/src/cli/lib/deploymentSelection.ts +2 -2
- package/src/cli/lib/generateDocs.test.ts +1 -1
- package/src/cli/lib/generateDocs.ts +3 -3
- package/src/cli/lib/generatedFunctionLogsApi.ts +45 -45
- package/src/cli/lib/localDeployment/projectMismatch.ts +2 -2
- package/src/cli/lib/login.ts +33 -30
- package/src/cli/lib/typecheck.test.ts +71 -0
- package/src/cli/lib/typecheck.ts +33 -27
- package/src/cli/lib/usageLimits.ts +242 -0
- package/src/cli/usageLimits.ts +420 -0
- package/src/index.ts +1 -1
- package/src/react/ConvexAuthState.test.tsx +24 -8
- package/src/react/auth_websocket.test.tsx +39 -29
- package/src/react/client.ts +3 -2
- package/src/react/client_provided_base_client.test.tsx +154 -36
- package/src/server/audit_logging.test.ts +2 -0
- package/src/server/components/index.test.ts +33 -1
- package/src/server/components/index.ts +41 -14
- package/src/server/cron.test.ts +58 -0
- package/src/server/cron.ts +82 -21
- package/src/server/database.ts +17 -0
- package/src/server/impl/database_impl.ts +4 -1
- package/src/server/log.ts +4 -1
- package/src/server/logVars.ts +17 -1
- package/src/server/meta.ts +2 -2
- package/src/server/schema.test.ts +6 -1
- package/src/values/compare.ts +5 -2
- package/src/values/index.ts +2 -1
- package/src/values/size.test.ts +5 -0
- package/src/values/size.ts +5 -1
- package/src/values/validator.test.ts +16 -1
- package/src/values/validator.ts +13 -0
- package/src/values/validators.ts +29 -1
- package/src/values/value.test.ts +35 -0
- 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 = (
|
|
21
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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(
|
|
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
|
);
|
package/src/react/client.ts
CHANGED
|
@@ -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?:
|
|
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?:
|
|
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
|
-
|
|
11
|
-
|
|
12
|
-
//
|
|
13
|
-
|
|
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
|
-
|
|
17
|
-
|
|
18
|
-
|
|
18
|
+
const testBaseConvexClient = () =>
|
|
19
|
+
new MinimalBaseConvexClient(address, () => {}, {
|
|
20
|
+
webSocketConstructor: ws as unknown as typeof WebSocket,
|
|
21
|
+
});
|
|
19
22
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
35
|
-
|
|
36
|
-
|
|
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
|
-
|
|
40
|
-
|
|
41
|
-
|
|
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
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
-
|
|
54
|
-
|
|
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 {
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
146
|
-
? {
|
|
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
|
|
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
|
|
160
|
-
|
|
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
|
|
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
|
|
219
|
-
|
|
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
|
-
|
|
299
|
+
...args: UseArgs<Definition>
|
|
273
300
|
): InstalledComponent<Definition>;
|
|
274
301
|
|
|
275
302
|
/**
|