Skip to content

Commit c5ba52c

Browse files
thomasballingerConvex, Inc.
authored andcommitted
ConvexReactClient.prewarmQuery({query, args}) (#39826)
Today this is just a more convenient and discoverable way to write ``` const watch = this.watchQuery(query, args); const unsubscribe = watch.onUpdate(() => {}); setTimeout(unsubscribe, 5000); ``` The behavior of this method might change in the future, making it more like a browser prefetch where subscribing is not guaranteed in some cases, e.g. low bandwidth client or high server load. GitOrigin-RevId: 0180c833200bcf6cc10c481526dbe14fa218ad8b
1 parent 017f3e6 commit c5ba52c

File tree

26 files changed

+1071
-107
lines changed

26 files changed

+1071
-107
lines changed

npm-packages/common/config/rush/pnpm-lock.yaml

Lines changed: 147 additions & 107 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

npm-packages/convex/CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,15 @@
22

33
## Unreleased
44

5+
- `ConvexReactClient.prewarmQuery({query, args})` method for subscribing to a
6+
query for 5 seconds. Prewarming indicates likely future interest in a
7+
subscription and is currently implemented by subscribing to the query for 5
8+
seconds.
9+
10+
The return value of this method may change and the arguments may change in the
11+
future so this API should be considered unstable but adapting to these changes
12+
shouldn't be difficult.
13+
514
- Expose the schemaValidation property of schema, intended for runtime tests or
615
assertions that it is indeed enabled.
716

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { test } from "vitest";
2+
import { makeFunctionReference } from "../server/index.js";
3+
import { EmptyObject } from "../server/registration.js";
4+
import { ConvexReactClient } from "../react/client.js";
5+
import { convexQueryOptions } from "./query_options.js";
6+
7+
const apiQueryFuncWithArgs = makeFunctionReference<
8+
"query",
9+
{ name: string },
10+
string
11+
>("jeans style");
12+
const apiQueryFuncWithoutArgs = makeFunctionReference<
13+
"query",
14+
EmptyObject,
15+
string
16+
>("jeans style");
17+
18+
test("convexQueryOptions", async () => {
19+
const _opts = convexQueryOptions({
20+
query: apiQueryFuncWithArgs,
21+
args: { name: "hey" },
22+
});
23+
24+
// @ts-expect-error This should be an error
25+
const _opts2 = convexQueryOptions({
26+
query: apiQueryFuncWithArgs,
27+
});
28+
29+
const _opts3 = convexQueryOptions({
30+
query: apiQueryFuncWithoutArgs,
31+
args: {},
32+
});
33+
34+
// @ts-expect-error For now args are always required, even at the top level.
35+
const _opts4 = convexQueryOptions({
36+
query: apiQueryFuncWithoutArgs,
37+
});
38+
39+
const _opts5 = convexQueryOptions({
40+
query: apiQueryFuncWithoutArgs,
41+
// @ts-expect-error This should be an error
42+
args: { name: "hey" },
43+
});
44+
});
45+
46+
test("prewarmQuery types", async () => {
47+
const client = {
48+
prewarmQuery: () => {},
49+
} as unknown as ConvexReactClient;
50+
51+
client.prewarmQuery({ query: apiQueryFuncWithArgs, args: { name: "hi" } });
52+
});
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/**
2+
* Query options are a potential new API for a variety of functions, but in particular a new overload of the React hook for queries.
3+
*
4+
* Inspired by https://tanstack.com/query/v5/docs/framework/react/guides/query-options
5+
*/
6+
import type { FunctionArgs, FunctionReference } from "../server/api.js";
7+
8+
// TODO if this type can encompass all use cases we can add not requiring args for queries
9+
// that don't take arguments. Goal would be that queryOptions allows leaving out args,
10+
// but queryOptions returns an object that always contains args. Helpers, "middleware,"
11+
// anything that intercepts these arguments
12+
/**
13+
* Query options.
14+
*/
15+
export type ConvexQueryOptions<Query extends FunctionReference<"query">> = {
16+
query: Query;
17+
args: FunctionArgs<Query>;
18+
extendSubscriptionFor?: number;
19+
};
20+
21+
// This helper helps more once we have more inference happening.
22+
export function convexQueryOptions<Query extends FunctionReference<"query">>(
23+
options: ConvexQueryOptions<Query>,
24+
): ConvexQueryOptions<Query> {
25+
return options;
26+
}

npm-packages/convex/src/react/client.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,11 @@ import {
2727
instantiateNoopLogger,
2828
Logger,
2929
} from "../browser/logging.js";
30+
import { ConvexQueryOptions } from "../browser/query_options.js";
31+
32+
// When no arguments are passed, extend subscriptions (for APIs that do this by default)
33+
// for this amount after the subscription would otherwise be dropped.
34+
const DEFAULT_EXTEND_SUBSCRIPTION_FOR = 5_000;
3035

3136
if (typeof React === "undefined") {
3237
throw new Error("Required dependency 'react' not found");
@@ -427,6 +432,33 @@ export class ConvexReactClient {
427432
};
428433
}
429434

435+
// Let's try out a queryOptions-style API.
436+
// This method is similar to the React Query API `queryClient.prefetchQuery()`.
437+
// In the future an ensureQueryData(): Promise<Data> method could exist.
438+
/**
439+
* Indicates likely future interest in a query subscription.
440+
*
441+
* The implementation currently immediately subscribes to a query. In the future this method
442+
* may prioritize some queries over others, fetch the query result without subscribing, or
443+
* do nothing in slow network connections or high load scenarios.
444+
*
445+
* To use this in a React component, call useQuery() and ignore the return value.
446+
*
447+
* @param queryOptions - A query (function reference from an api object) and its args, plus
448+
* an optional extendSubscriptionFor for how long to subscribe to the query.
449+
*/
450+
prewarmQuery<Query extends FunctionReference<"query">>(
451+
queryOptions: ConvexQueryOptions<Query> & {
452+
extendSubscriptionFor?: number;
453+
},
454+
) {
455+
const extendSubscriptionFor =
456+
queryOptions.extendSubscriptionFor ?? DEFAULT_EXTEND_SUBSCRIPTION_FOR;
457+
const watch = this.watchQuery(queryOptions.query, queryOptions.args || {});
458+
const unsubscribe = watch.onUpdate(() => {});
459+
setTimeout(unsubscribe, extendSubscriptionFor);
460+
}
461+
430462
/**
431463
* Execute a mutation function.
432464
*

npm-packages/demos/prewarming/.env

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# To access values in this file from the frontend with Vite, use
2+
# import.meta.env.VARIABLE_NAME_HERE
3+
# this line and below will be removed when publishing demos
4+
VITE_CONVEX_URL="http://127.0.0.1:8000"
5+
# this line and above will be removed when publishing demos
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
node_modules
2+
.DS_Store
3+
dist
4+
.env.local
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# TypeScript and Schemas Example App
2+
3+
This example demonstrates how to write a Convex app in
4+
[TypeScript](https://docs.convex.dev/using/typescript).
5+
6+
The Convex functions are written in `.ts` files and the React components use
7+
`.tsx`.
8+
9+
This app also defines a Convex
10+
[schema](https://docs.convex.dev/database/schemas) in `convex/schema.ts` to
11+
create TypeScript types specific to the app's data model.
12+
13+
## Running the App
14+
15+
Run:
16+
17+
```
18+
npm install
19+
npm run dev
20+
```
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Welcome to your Convex functions directory!
2+
3+
Write your Convex functions here.
4+
See https://docs.convex.dev/functions for more.
5+
6+
A query function that takes two arguments looks like:
7+
8+
```ts
9+
// convex/myFunctions.ts
10+
import { query } from "./_generated/server";
11+
import { v } from "convex/values";
12+
13+
export const myQueryFunction = query({
14+
// Validators for arguments.
15+
args: {
16+
first: v.number(),
17+
second: v.string(),
18+
},
19+
20+
// Function implementation.
21+
handler: async (ctx, args) => {
22+
// Read the database as many times as you need here.
23+
// See https://docs.convex.dev/database/reading-data.
24+
const documents = await ctx.db.query("tablename").collect();
25+
26+
// Arguments passed from the client are properties of the args object.
27+
console.log(args.first, args.second);
28+
29+
// Write arbitrary JavaScript here: filter, aggregate, build derived data,
30+
// remove non-public properties, or create new objects.
31+
return documents;
32+
},
33+
});
34+
```
35+
36+
Using this query function in a React component looks like:
37+
38+
```ts
39+
const data = useQuery(api.myFunctions.myQueryFunction, {
40+
first: 10,
41+
second: "hello",
42+
});
43+
```
44+
45+
A mutation function looks like:
46+
47+
```ts
48+
// convex/myFunctions.ts
49+
import { mutation } from "./_generated/server";
50+
import { v } from "convex/values";
51+
52+
export const myMutationFunction = mutation({
53+
// Validators for arguments.
54+
args: {
55+
first: v.string(),
56+
second: v.string(),
57+
},
58+
59+
// Function implementation.
60+
handler: async (ctx, args) => {
61+
// Insert or modify documents in the database here.
62+
// Mutations can also read from the database like queries.
63+
// See https://docs.convex.dev/database/writing-data.
64+
const message = { body: args.first, author: args.second };
65+
const id = await ctx.db.insert("messages", message);
66+
67+
// Optionally, return a value from your mutation.
68+
return await ctx.db.get(id);
69+
},
70+
});
71+
```
72+
73+
Using this mutation function in a React component looks like:
74+
75+
```ts
76+
const mutation = useMutation(api.myFunctions.myMutationFunction);
77+
function handleButtonPress() {
78+
// fire and forget, the most common way to use mutations
79+
mutation({ first: "Hello!", second: "me" });
80+
// OR
81+
// use the result once the mutation has completed
82+
mutation({ first: "Hello!", second: "me" }).then((result) =>
83+
console.log(result),
84+
);
85+
}
86+
```
87+
88+
Use the Convex CLI to push your functions to a deployment. See everything
89+
the Convex CLI can do by running `npx convex -h` in your project root
90+
directory. To learn more, launch the docs with `npx convex docs`.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/* eslint-disable */
2+
/**
3+
* Generated `api` utility.
4+
*
5+
* THIS CODE IS AUTOMATICALLY GENERATED.
6+
*
7+
* To regenerate, run `npx convex dev`.
8+
* @module
9+
*/
10+
11+
import type {
12+
ApiFromModules,
13+
FilterApi,
14+
FunctionReference,
15+
} from "convex/server";
16+
import type * as messages from "../messages.js";
17+
18+
/**
19+
* A utility for referencing Convex functions in your app's API.
20+
*
21+
* Usage:
22+
* ```js
23+
* const myFunctionReference = api.myModule.myFunction;
24+
* ```
25+
*/
26+
declare const fullApi: ApiFromModules<{
27+
messages: typeof messages;
28+
}>;
29+
export declare const api: FilterApi<
30+
typeof fullApi,
31+
FunctionReference<any, "public">
32+
>;
33+
export declare const internal: FilterApi<
34+
typeof fullApi,
35+
FunctionReference<any, "internal">
36+
>;

0 commit comments

Comments
 (0)