convex 1.42.2-alpha.0 → 1.42.3

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 (75) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/dist/browser.bundle.js +1 -1
  3. package/dist/browser.bundle.js.map +2 -2
  4. package/dist/cjs/browser/index.js.map +2 -2
  5. package/dist/cjs/browser/sync/client.js.map +1 -1
  6. package/dist/cjs/browser/sync/paginated_query_client.js.map +2 -2
  7. package/dist/cjs/cli/codegen_templates/common.js +6 -0
  8. package/dist/cjs/cli/codegen_templates/common.js.map +2 -2
  9. package/dist/cjs/cli/codegen_templates/component_api.js +2 -2
  10. package/dist/cjs/cli/codegen_templates/component_api.js.map +2 -2
  11. package/dist/cjs/cli/lib/codegen.js +2 -1
  12. package/dist/cjs/cli/lib/codegen.js.map +2 -2
  13. package/dist/cjs/index.js +1 -1
  14. package/dist/cjs/index.js.map +1 -1
  15. package/dist/cjs/react/client.js.map +2 -2
  16. package/dist/cjs/server/meta.js.map +1 -1
  17. package/dist/cjs-types/browser/index.d.ts.map +1 -1
  18. package/dist/cjs-types/browser/sync/paginated_query_client.d.ts +1 -38
  19. package/dist/cjs-types/browser/sync/paginated_query_client.d.ts.map +1 -1
  20. package/dist/cjs-types/cli/codegen_templates/common.d.ts +12 -0
  21. package/dist/cjs-types/cli/codegen_templates/common.d.ts.map +1 -1
  22. package/dist/cjs-types/cli/codegen_templates/common.test.d.ts +2 -0
  23. package/dist/cjs-types/cli/codegen_templates/common.test.d.ts.map +1 -0
  24. package/dist/cjs-types/cli/codegen_templates/component_api.d.ts.map +1 -1
  25. package/dist/cjs-types/cli/lib/codegen.d.ts.map +1 -1
  26. package/dist/cjs-types/index.d.ts +1 -1
  27. package/dist/cjs-types/index.d.ts.map +1 -1
  28. package/dist/cjs-types/react/client.d.ts.map +1 -1
  29. package/dist/cjs-types/server/meta.d.ts +10 -2
  30. package/dist/cjs-types/server/meta.d.ts.map +1 -1
  31. package/dist/cli.bundle.cjs +9 -4
  32. package/dist/cli.bundle.cjs.map +2 -2
  33. package/dist/esm/browser/index.js.map +2 -2
  34. package/dist/esm/browser/sync/client.js.map +1 -1
  35. package/dist/esm/browser/sync/paginated_query_client.js.map +2 -2
  36. package/dist/esm/cli/codegen_templates/common.js +5 -0
  37. package/dist/esm/cli/codegen_templates/common.js.map +2 -2
  38. package/dist/esm/cli/codegen_templates/component_api.js +8 -3
  39. package/dist/esm/cli/codegen_templates/component_api.js.map +2 -2
  40. package/dist/esm/cli/lib/codegen.js +2 -1
  41. package/dist/esm/cli/lib/codegen.js.map +2 -2
  42. package/dist/esm/index.js +1 -1
  43. package/dist/esm/index.js.map +1 -1
  44. package/dist/esm/react/client.js.map +2 -2
  45. package/dist/esm-types/browser/index.d.ts.map +1 -1
  46. package/dist/esm-types/browser/sync/paginated_query_client.d.ts +1 -38
  47. package/dist/esm-types/browser/sync/paginated_query_client.d.ts.map +1 -1
  48. package/dist/esm-types/cli/codegen_templates/common.d.ts +12 -0
  49. package/dist/esm-types/cli/codegen_templates/common.d.ts.map +1 -1
  50. package/dist/esm-types/cli/codegen_templates/common.test.d.ts +2 -0
  51. package/dist/esm-types/cli/codegen_templates/common.test.d.ts.map +1 -0
  52. package/dist/esm-types/cli/codegen_templates/component_api.d.ts.map +1 -1
  53. package/dist/esm-types/cli/lib/codegen.d.ts.map +1 -1
  54. package/dist/esm-types/index.d.ts +1 -1
  55. package/dist/esm-types/index.d.ts.map +1 -1
  56. package/dist/esm-types/react/client.d.ts.map +1 -1
  57. package/dist/esm-types/server/meta.d.ts +10 -2
  58. package/dist/esm-types/server/meta.d.ts.map +1 -1
  59. package/dist/react.bundle.js +1 -1
  60. package/dist/react.bundle.js.map +2 -2
  61. package/package.json +1 -1
  62. package/src/browser/index.ts +2 -0
  63. package/src/browser/sync/client.test.ts +8 -1
  64. package/src/browser/sync/client.ts +63 -0
  65. package/src/browser/sync/paginated_query_client.ts +3 -2
  66. package/src/cli/codegen_templates/common.test.ts +52 -0
  67. package/src/cli/codegen_templates/common.ts +17 -0
  68. package/src/cli/codegen_templates/component_api.ts +8 -3
  69. package/src/cli/lib/codegen.ts +2 -1
  70. package/src/index.ts +1 -1
  71. package/src/react/ConvexAuthState.test.tsx +24 -8
  72. package/src/react/auth_websocket.test.tsx +39 -29
  73. package/src/react/client.ts +3 -2
  74. package/src/react/client_provided_base_client.test.tsx +154 -36
  75. package/src/server/meta.ts +2 -4
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "convex",
3
3
  "description": "Client for the Convex Cloud",
4
- "version": "1.42.2-alpha.0",
4
+ "version": "1.42.3",
5
5
  "author": "Convex, Inc. <no-reply@convex.dev>",
6
6
  "homepage": "https://convex.dev",
7
7
  "repository": {
@@ -24,6 +24,8 @@ export type {
24
24
  ConnectionState,
25
25
  AuthTokenFetcher,
26
26
  } from "./sync/client.js";
27
+ /** @internal */
28
+ export type { BaseConvexClientInterface } from "./sync/client.js";
27
29
  export type { PaginationStatus } from "./sync/pagination.js";
28
30
  export type { ConvexClientOptions } from "./simple_client.js";
29
31
  export { ConvexClient } from "./simple_client.js";
@@ -4,9 +4,16 @@
4
4
 
5
5
  import { test, expect } from "vitest";
6
6
 
7
- import { BaseConvexClient } from "./client.js";
7
+ import { BaseConvexClient, BaseConvexClientInterface } from "./client.js";
8
8
  import { anyApi } from "../../server/api.js";
9
9
 
10
+ // Compile-time assertion that `BaseConvexClient` satisfies
11
+ // `BaseConvexClientInterface`. We can't use an `implements` clause on the class
12
+ // because `BaseConvexClientInterface` is `@internal`: that would leak the
13
+ // interface name into the public `.d.ts` where it has been stripped.
14
+ const _assertBaseConvexClientImplementsInterface: BaseConvexClientInterface =
15
+ undefined as unknown as BaseConvexClient;
16
+
10
17
  test("localQueryResult reflects optimistic results", async () => {
11
18
  const client = new BaseConvexClient("http://127.0.0.1:8000", () => {
12
19
  // ignore updates.
@@ -1126,3 +1126,66 @@ export class BaseConvexClient {
1126
1126
  });
1127
1127
  }
1128
1128
  }
1129
+
1130
+ /**
1131
+ * The public API of {@link BaseConvexClient}.
1132
+ *
1133
+ * @internal
1134
+ */
1135
+ export interface BaseConvexClientInterface {
1136
+ addOnTransitionHandler(fn: (transition: Transition) => void): () => void;
1137
+
1138
+ setAuth(
1139
+ fetchToken: AuthTokenFetcher,
1140
+ onChange: (isAuthenticated: boolean) => void,
1141
+ onRefreshChange?: (isRefreshing: boolean) => void,
1142
+ ): void;
1143
+
1144
+ /** @internal */
1145
+ setAdminAuth(value: string, fakeUserIdentity?: UserIdentityAttributes): void;
1146
+
1147
+ clearAuth(): void;
1148
+
1149
+ subscribe(
1150
+ name: string,
1151
+ args?: Record<string, Value>,
1152
+ options?: SubscribeOptions,
1153
+ ): { queryToken: QueryToken; unsubscribe: () => void };
1154
+
1155
+ localQueryResult(
1156
+ udfPath: string,
1157
+ args?: Record<string, Value>,
1158
+ ): Value | undefined;
1159
+
1160
+ localQueryResultByToken(queryToken: QueryToken): Value | undefined;
1161
+
1162
+ hasLocalQueryResultByToken(queryToken: QueryToken): boolean;
1163
+
1164
+ localQueryLogs(
1165
+ udfPath: string,
1166
+ args?: Record<string, Value>,
1167
+ ): string[] | undefined;
1168
+
1169
+ queryJournal(
1170
+ name: string,
1171
+ args?: Record<string, Value>,
1172
+ ): QueryJournal | undefined;
1173
+
1174
+ connectionState(): ConnectionState;
1175
+
1176
+ subscribeToConnectionState(
1177
+ cb: (connectionState: ConnectionState) => void,
1178
+ ): () => void;
1179
+
1180
+ mutation(
1181
+ name: string,
1182
+ args?: Record<string, Value>,
1183
+ options?: MutationOptions,
1184
+ ): Promise<any>;
1185
+
1186
+ action(name: string, args?: Record<string, Value>): Promise<any>;
1187
+
1188
+ close(): Promise<void>;
1189
+
1190
+ readonly url: string;
1191
+ }
@@ -17,7 +17,7 @@ import {
17
17
  serializePaginatedPathAndArgs,
18
18
  canonicalizeUdfPath,
19
19
  } from "./udf_path_utils.js";
20
- import { BaseConvexClient, Transition } from "./client.js";
20
+ import { BaseConvexClientInterface, Transition } from "./client.js";
21
21
  import {
22
22
  PaginatedQueryResult,
23
23
  PaginationStatus,
@@ -74,6 +74,7 @@ export type ExtendedTransition = Transition & {
74
74
  }>;
75
75
  };
76
76
 
77
+ /** @internal */
77
78
  export class PaginatedQueryClient {
78
79
  private paginatedQuerySet: Map<PaginatedQueryToken, LocalPaginatedQuery> =
79
80
  new Map();
@@ -81,7 +82,7 @@ export class PaginatedQueryClient {
81
82
  private lastTransitionTs: TS;
82
83
 
83
84
  constructor(
84
- private client: BaseConvexClient,
85
+ private client: BaseConvexClientInterface,
85
86
  private onTransition: (transition: ExtendedTransition) => void,
86
87
  ) {
87
88
  // Nonsense initial value to construct synthetic Transitions
@@ -0,0 +1,52 @@
1
+ import { describe, test, expect } from "vitest";
2
+ import { compareModulePaths } from "./common.js";
3
+
4
+ describe("compareModulePaths", () => {
5
+ test("matches default sort order for forward slash paths", () => {
6
+ const paths = ["fooBaz.ts", "foo/bar.ts", "foo.ts"];
7
+ expect([...paths].sort(compareModulePaths)).toEqual([...paths].sort());
8
+ });
9
+
10
+ test("orders Windows paths identically to their POSIX equivalents", () => {
11
+ const posixPaths = ["fooBaz.ts", "foo/bar.ts", "foo.ts"];
12
+ const windowsPaths = posixPaths.map((p) => p.replace(/\//g, "\\"));
13
+
14
+ const sortedPosix = [...posixPaths].sort(compareModulePaths);
15
+ const sortedWindows = [...windowsPaths].sort(compareModulePaths);
16
+
17
+ expect(sortedPosix).toEqual(["foo.ts", "foo/bar.ts", "fooBaz.ts"]);
18
+ expect(sortedWindows.map((p) => p.replace(/\\/g, "/"))).toEqual(
19
+ sortedPosix,
20
+ );
21
+ });
22
+
23
+ test("default sort diverges on this input, demonstrating the bug", () => {
24
+ // "/" (0x2F) sorts before letters while "\" (0x5C) sorts after them,
25
+ // so the OS-native forms of the same file set sort differently.
26
+ const windowsPaths = ["fooBaz.ts", "foo\\bar.ts", "foo.ts"];
27
+ expect([...windowsPaths].sort()).toEqual([
28
+ "foo.ts",
29
+ "fooBaz.ts",
30
+ "foo\\bar.ts",
31
+ ]);
32
+ expect([...windowsPaths].sort(compareModulePaths)).toEqual([
33
+ "foo.ts",
34
+ "foo\\bar.ts",
35
+ "fooBaz.ts",
36
+ ]);
37
+ });
38
+
39
+ test("sorts nested directories before longer sibling file names", () => {
40
+ const paths = ["a/b/c.ts", "aZ.ts", "a.ts", "a/b.ts"];
41
+ expect([...paths].sort(compareModulePaths)).toEqual([
42
+ "a.ts",
43
+ "a/b.ts",
44
+ "a/b/c.ts",
45
+ "aZ.ts",
46
+ ]);
47
+ const windows = paths.map((p) => p.replace(/\//g, "\\"));
48
+ expect(
49
+ [...windows].sort(compareModulePaths).map((p) => p.replace(/\\/g, "/")),
50
+ ).toEqual(["a.ts", "a/b.ts", "a/b/c.ts", "aZ.ts"]);
51
+ });
52
+ });
@@ -43,3 +43,20 @@ const collator = new Intl.Collator("en-US", {
43
43
  export function compareStrings(a: string, b: string): number {
44
44
  return collator.compare(a, b);
45
45
  }
46
+
47
+ /**
48
+ * Comparison function for sorting module paths in codegen output.
49
+ *
50
+ * Compares the forward slash normalized form of each path so the resulting
51
+ * order is identical on every platform. Sorting OS-native paths directly
52
+ * diverges on Windows because "\" (0x5C) sorts after letters while "/"
53
+ * (0x2F) sorts before them. Uses plain code unit comparison to match the
54
+ * default Array.prototype.sort() order these paths sorted with on POSIX.
55
+ *
56
+ * Usage: modulePaths.sort(compareModulePaths)
57
+ */
58
+ export function compareModulePaths(a: string, b: string): number {
59
+ const aNormalized = a.replace(/\\/g, "/");
60
+ const bNormalized = b.replace(/\\/g, "/");
61
+ return aNormalized < bNormalized ? -1 : aNormalized > bNormalized ? 1 : 0;
62
+ }
@@ -9,7 +9,12 @@ import {
9
9
  } from "../lib/components/definition/directoryStructure.js";
10
10
  import { StartPushResponse } from "../lib/deployApi/startPush.js";
11
11
  import { importPath, moduleIdentifier } from "./api.js";
12
- import { apiComment, compareStrings, header } from "./common.js";
12
+ import {
13
+ apiComment,
14
+ compareModulePaths,
15
+ compareStrings,
16
+ header,
17
+ } from "./common.js";
13
18
  import {
14
19
  ComponentExports,
15
20
  EvaluatedComponentDefinition,
@@ -346,7 +351,7 @@ async function* codegenDynamicApiObjects(
346
351
  const absModulePaths = await entryPoints(ctx, componentDirectory.path);
347
352
  const modulePaths = absModulePaths
348
353
  .map((p) => path.relative(componentDirectory.path, p))
349
- .sort();
354
+ .sort(compareModulePaths);
350
355
  for (const modulePath of modulePaths) {
351
356
  const ident = moduleIdentifier(modulePath);
352
357
  const path = importPath(modulePath);
@@ -382,7 +387,7 @@ async function* codegenDynamicApiObjectsTS(
382
387
  const absModulePaths = await entryPoints(ctx, componentDirectory.path);
383
388
  const modulePaths = absModulePaths
384
389
  .map((p) => path.relative(componentDirectory.path, p))
385
- .sort();
390
+ .sort(compareModulePaths);
386
391
  for (const modulePath of modulePaths) {
387
392
  const ident = moduleIdentifier(modulePath);
388
393
  const path = importPath(modulePath);
@@ -4,6 +4,7 @@ import { withTmpDir, TempDir } from "../../bundler/fs.js";
4
4
  import { entryPoints } from "../../bundler/index.js";
5
5
  import { apiCodegen } from "../codegen_templates/api.js";
6
6
  import { apiCjsCodegen } from "../codegen_templates/api_cjs.js";
7
+ import { compareModulePaths } from "../codegen_templates/common.js";
7
8
  import {
8
9
  dynamicDataModelDTS,
9
10
  dynamicDataModelTS,
@@ -846,7 +847,7 @@ async function doApiCodegen(
846
847
  const absModulePaths = await entryPoints(ctx, functionsDir);
847
848
  const modulePaths = absModulePaths
848
849
  .map((p) => path.relative(functionsDir, p))
849
- .sort();
850
+ .sort(compareModulePaths);
850
851
 
851
852
  const writtenFiles: string[] = [];
852
853
 
package/src/index.ts CHANGED
@@ -1 +1 @@
1
- export const version = "1.42.2-alpha.0";
1
+ export const version = "1.42.3";
@@ -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
 
@@ -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;