convex-helpers 0.1.41 → 0.1.42

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/README.md CHANGED
@@ -17,6 +17,7 @@ Table of contents:
17
17
  - [CRUD](#crud-utilities)
18
18
  - [Validator utilities](#validator-utilities)
19
19
  - [Filter db queries with JS](#filter)
20
+ - [Query caching with ConvexQueryCacheProvider](#query-caching)
20
21
 
21
22
  ## Custom Functions
22
23
 
@@ -373,10 +374,13 @@ By default, `useQuery` will throw an error when the server throws. It also
373
374
  returns `undefined` to indicate a "loading" state. This helper returns:
374
375
 
375
376
  ```ts
376
- const { status, data, error, isSuccess, isPending, isError } = useQuery(
377
- api.foo.bar,
378
- { myArg: 123 },
379
- );
377
+ import { makeUseQueryWithStatus } from "convex-helpers/react";
378
+ import { useQueries } from "convex/react";
379
+ // Do this once somewhere, name it whatever you want.
380
+ export const useQueryWithStatus = makeUseQueryWithStatus(useQueries);
381
+
382
+ const { status, data, error, isSuccess, isPending, isError } =
383
+ useQueryWithStatus(api.foo.bar, { myArg: 123 });
380
384
  ```
381
385
 
382
386
  The types of the return is:
@@ -630,3 +634,64 @@ export const lastCountLongerThanName = query({
630
634
  },
631
635
  });
632
636
  ```
637
+
638
+ ## Query Caching
639
+
640
+ Utilize a query cache implementation which persists subscriptions to the
641
+ server for some expiration period even after app `useQuery` hooks have all
642
+ unmounted. This allows very fast reloading of unevicted values during
643
+ navigation changes, view changes, etc.
644
+
645
+ Related files:
646
+
647
+ - [provider.tsx](./react/cache/provider.tsx) contains `ConvexQueryCacheProvider`,
648
+ a configurable cache provider you put in your react app's root.
649
+ - [hooks.ts](./react/cache/hooks.ts) contains cache-enabled drop-in
650
+ replacements for both `useQuery` and `useQueries` from `convex/react`.
651
+
652
+ To use the cache, first make sure to put a `<ConvexQueryCacheProvider>`
653
+ inside `<ConvexProvider>` in your react component tree:
654
+
655
+ ```jsx
656
+
657
+ import { ConvexQueryCacheProvider } from "convex-helpers/react/cache/provider";
658
+
659
+ //...
660
+
661
+ export default function RootLayout({
662
+ children,
663
+ }: Readonly<{
664
+ children: React.ReactNode;
665
+ }>) {
666
+ return (
667
+ <html lang="en">
668
+ <body className={inter.className}>
669
+ <ConvexClientProvider>
670
+ <ConvexQueryCacheProvider>{children}</ConvexQueryCacheProvider>
671
+ </ConvexClientProvider>
672
+ </body>
673
+ </html>
674
+ );
675
+ }
676
+ ```
677
+
678
+ This provider takes three optional props:
679
+
680
+ - **expiration** (number) -- Milliseconds to preserve unmounted subscriptions
681
+ in the cache. After this, the subscriptions will be dropped, and the value
682
+ will have to be re-fetched from the server. (Default: 300000, aka 5 minutes)
683
+ - **maxIdleEntires** (number) -- Maximum number of unused subscriptions
684
+ kept in the cache. (Default: 250).
685
+ - **debug** (boolean) -- Dump console logs every 3s to debug the state of
686
+ the cache (Default: false).
687
+
688
+ Finally, you can utilize `useQuery` (and `useQueries`) just the same as
689
+ their `convex/react` equivalents.
690
+
691
+ ```jsx
692
+ import { useQuery } from "convex-helpers/react/cache/hooks";
693
+
694
+ // ...
695
+
696
+ const users = useQuery(api.users.getAll);
697
+ ```
@@ -0,0 +1,73 @@
1
+ import { OptionalRestArgsOrSkip, RequestForQueries } from "convex/react";
2
+ import { FunctionReference, FunctionReturnType } from "convex/server";
3
+ /**
4
+ * Load a variable number of reactive Convex queries, utilizing
5
+ * the query cache.
6
+ *
7
+ * `useQueries` is similar to {@link useQuery} but it allows
8
+ * loading multiple queries which can be useful for loading a dynamic number
9
+ * of queries without violating the rules of React hooks.
10
+ *
11
+ * This hook accepts an object whose keys are identifiers for each query and the
12
+ * values are objects of `{ query: FunctionReference, args: Record<string, Value> }`. The
13
+ * `query` is a FunctionReference for the Convex query function to load, and the `args` are
14
+ * the arguments to that function.
15
+ *
16
+ * The hook returns an object that maps each identifier to the result of the query,
17
+ * `undefined` if the query is still loading, or an instance of `Error` if the query
18
+ * threw an exception.
19
+ *
20
+ * For example if you loaded a query like:
21
+ * ```typescript
22
+ * const results = useQueries({
23
+ * messagesInGeneral: {
24
+ * query: "listMessages",
25
+ * args: { channel: "#general" }
26
+ * }
27
+ * });
28
+ * ```
29
+ * then the result would look like:
30
+ * ```typescript
31
+ * {
32
+ * messagesInGeneral: [{
33
+ * channel: "#general",
34
+ * body: "hello"
35
+ * _id: ...,
36
+ * _creationTime: ...
37
+ * }]
38
+ * }
39
+ * ```
40
+ *
41
+ * This React hook contains internal state that will cause a rerender
42
+ * whenever any of the query results change.
43
+ *
44
+ * Throws an error if not used under {@link ConvexProvider}.
45
+ *
46
+ * @param queries - An object mapping identifiers to objects of
47
+ * `{query: string, args: Record<string, Value> }` describing which query
48
+ * functions to fetch.
49
+ * @returns An object with the same keys as the input. The values are the result
50
+ * of the query function, `undefined` if it's still loading, or an `Error` if
51
+ * it threw an exception.
52
+ *
53
+ * @public
54
+ */
55
+ export declare function useQueries(queries: RequestForQueries): Record<string, any | undefined | Error>;
56
+ /**
57
+ * Load a reactive query within a React component.
58
+ *
59
+ * This React hook contains internal state that will cause a rerender
60
+ * whenever the query result changes.
61
+ *
62
+ * Throws an error if not used under {@link ConvexProvider} and {@link ConvexQueryCacheProvider}.
63
+ *
64
+ * @param query - a {@link FunctionReference} for the public query to run
65
+ * like `api.dir1.dir2.filename.func`.
66
+ * @param args - The arguments to the query function or the string "skip" if the
67
+ * query should not be loaded.
68
+ * @returns the result of the query. If the query is loading returns `undefined`.
69
+ *
70
+ * @public
71
+ */
72
+ export declare function useQuery<Query extends FunctionReference<"query">>(query: Query, ...queryArgs: OptionalRestArgsOrSkip<Query>): FunctionReturnType<Query>;
73
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../react/cache/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,sBAAsB,EACtB,iBAAiB,EAClB,MAAM,cAAc,CAAC;AACtB,OAAO,EAEL,iBAAiB,EACjB,kBAAkB,EAEnB,MAAM,eAAe,CAAC;AAKvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAmDG;AACH,wBAAgB,UAAU,CACxB,OAAO,EAAE,iBAAiB,GACzB,MAAM,CAAC,MAAM,EAAE,GAAG,GAAG,SAAS,GAAG,KAAK,CAAC,CA4CzC;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,QAAQ,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC,EAC/D,KAAK,EAAE,KAAK,EACZ,GAAG,SAAS,EAAE,sBAAsB,CAAC,KAAK,CAAC,GAC1C,kBAAkB,CAAC,KAAK,CAAC,CAoB3B"}
@@ -0,0 +1,140 @@
1
+ import { getFunctionName, } from "convex/server";
2
+ import { useContext, useEffect, useState } from "react";
3
+ import { ConvexQueryCacheContext } from "./provider.js";
4
+ import { convexToJson } from "convex/values";
5
+ /**
6
+ * Load a variable number of reactive Convex queries, utilizing
7
+ * the query cache.
8
+ *
9
+ * `useQueries` is similar to {@link useQuery} but it allows
10
+ * loading multiple queries which can be useful for loading a dynamic number
11
+ * of queries without violating the rules of React hooks.
12
+ *
13
+ * This hook accepts an object whose keys are identifiers for each query and the
14
+ * values are objects of `{ query: FunctionReference, args: Record<string, Value> }`. The
15
+ * `query` is a FunctionReference for the Convex query function to load, and the `args` are
16
+ * the arguments to that function.
17
+ *
18
+ * The hook returns an object that maps each identifier to the result of the query,
19
+ * `undefined` if the query is still loading, or an instance of `Error` if the query
20
+ * threw an exception.
21
+ *
22
+ * For example if you loaded a query like:
23
+ * ```typescript
24
+ * const results = useQueries({
25
+ * messagesInGeneral: {
26
+ * query: "listMessages",
27
+ * args: { channel: "#general" }
28
+ * }
29
+ * });
30
+ * ```
31
+ * then the result would look like:
32
+ * ```typescript
33
+ * {
34
+ * messagesInGeneral: [{
35
+ * channel: "#general",
36
+ * body: "hello"
37
+ * _id: ...,
38
+ * _creationTime: ...
39
+ * }]
40
+ * }
41
+ * ```
42
+ *
43
+ * This React hook contains internal state that will cause a rerender
44
+ * whenever any of the query results change.
45
+ *
46
+ * Throws an error if not used under {@link ConvexProvider}.
47
+ *
48
+ * @param queries - An object mapping identifiers to objects of
49
+ * `{query: string, args: Record<string, Value> }` describing which query
50
+ * functions to fetch.
51
+ * @returns An object with the same keys as the input. The values are the result
52
+ * of the query function, `undefined` if it's still loading, or an `Error` if
53
+ * it threw an exception.
54
+ *
55
+ * @public
56
+ */
57
+ export function useQueries(queries) {
58
+ const { registry } = useContext(ConvexQueryCacheContext);
59
+ if (registry === null) {
60
+ throw new Error("Could not find `ConvexQueryCacheContext`! This `useQuery` implementation must be used in the React component " +
61
+ "tree under `ConvexQueryCacheProvider`. Did you forget it? ");
62
+ }
63
+ const queryKeys = {};
64
+ const results = {};
65
+ for (const [key, { query, args }] of Object.entries(queries)) {
66
+ const queryKey = createQueryKey(query, args);
67
+ results[key] = registry.probe(queryKey);
68
+ queryKeys[key] = queryKey;
69
+ }
70
+ const [_, setValues] = useState(results);
71
+ useEffect(() => {
72
+ const ids = [];
73
+ for (const [key, { query, args }] of Object.entries(queries)) {
74
+ const id = crypto.randomUUID();
75
+ registry.start(id, queryKeys[key], query, args, (v) => setValues((old) => {
76
+ if (!(key in old))
77
+ return old; // We're no longer tracking this query
78
+ if (old[key] === v)
79
+ return old; // No change
80
+ return { ...old, [key]: v };
81
+ }));
82
+ ids.push(id);
83
+ }
84
+ return () => {
85
+ for (const id of ids) {
86
+ registry.end(id);
87
+ }
88
+ };
89
+ },
90
+ // Safe to ignore query and args since queryKey is derived from them
91
+ // eslint-disable-next-line react-hooks/exhaustive-deps
92
+ [registry, JSON.stringify(queryKeys)]);
93
+ return results;
94
+ }
95
+ /**
96
+ * Load a reactive query within a React component.
97
+ *
98
+ * This React hook contains internal state that will cause a rerender
99
+ * whenever the query result changes.
100
+ *
101
+ * Throws an error if not used under {@link ConvexProvider} and {@link ConvexQueryCacheProvider}.
102
+ *
103
+ * @param query - a {@link FunctionReference} for the public query to run
104
+ * like `api.dir1.dir2.filename.func`.
105
+ * @param args - The arguments to the query function or the string "skip" if the
106
+ * query should not be loaded.
107
+ * @returns the result of the query. If the query is loading returns `undefined`.
108
+ *
109
+ * @public
110
+ */
111
+ export function useQuery(query, ...queryArgs) {
112
+ const args = queryArgs[0] ?? {};
113
+ // Unlike the regular useQuery implementation, we don't need to memoize
114
+ // the params here, since the cached useQueries will handle that.
115
+ const results = useQueries(args === "skip"
116
+ ? {} // Use queries doesn't support skip.
117
+ : {
118
+ _default: { query, args },
119
+ });
120
+ // This may be undefined either because the upstream
121
+ // value is actually undefined, or because the value
122
+ // was not sent to `useQueries` due to "skip".
123
+ const result = results._default;
124
+ if (result instanceof Error) {
125
+ throw result;
126
+ }
127
+ return result;
128
+ }
129
+ /**
130
+ * Generate a query key from a query function and its arguments.
131
+ * @param query Query function reference like api.foo.bar
132
+ * @param args Arguments to the function, like { foo: "bar" }
133
+ * @returns A string key that uniquely identifies the query and its arguments.
134
+ */
135
+ function createQueryKey(query, args) {
136
+ const queryString = getFunctionName(query);
137
+ const key = [queryString, convexToJson(args)];
138
+ const queryKey = JSON.stringify(key);
139
+ return queryKey;
140
+ }
@@ -0,0 +1,69 @@
1
+ import { ConvexReactClient } from "convex/react";
2
+ import { FunctionArgs, FunctionReference, FunctionReturnType } from "convex/server";
3
+ import { FC, PropsWithChildren } from "react";
4
+ export declare const ConvexQueryCacheContext: import("react").Context<{
5
+ registry: CacheRegistry | null;
6
+ }>;
7
+ /**
8
+ * A provider that establishes a query cache context in the React render
9
+ * tree so that cached `useQuery` calls can be used.
10
+ *
11
+ * @component
12
+ * @param {ConvexQueryCacheOptions} props.options - Options for the query cache
13
+ * @returns {Element}
14
+ */
15
+ export declare const ConvexQueryCacheProvider: FC<PropsWithChildren<ConvexQueryCacheOptions>>;
16
+ export type ConvexQueryCacheOptions = {
17
+ /**
18
+ * How long, in milliseconds, to keep the subscription to the convex
19
+ * query alive even after all references in the app have been dropped.
20
+ *
21
+ * @default 300000
22
+ */
23
+ expiration?: number;
24
+ /**
25
+ * How many "extra" idle query subscriptions are allowed to remain
26
+ * connected to your convex backend.
27
+ *
28
+ * @default Infinity
29
+ */
30
+ maxIdleEntries?: number;
31
+ /**
32
+ * A debug flag that will cause information about the query cache
33
+ * to be logged to the console every 3 seconds.
34
+ *
35
+ * @default false
36
+ */
37
+ debug?: boolean;
38
+ };
39
+ /**
40
+ * Implementation of the query cache.
41
+ */
42
+ type SubKey = string;
43
+ type QueryKey = string;
44
+ type CachedQuery<Query extends FunctionReference<"query">> = {
45
+ refs: Set<string>;
46
+ evictTimer: number | null;
47
+ unsub?: () => void;
48
+ value?: FunctionReturnType<Query> | Error;
49
+ };
50
+ type SubEntry<Query extends FunctionReference<"query">> = {
51
+ queryKey: QueryKey;
52
+ setter: (v?: FunctionReturnType<Query>) => void;
53
+ };
54
+ declare class CacheRegistry {
55
+ #private;
56
+ queries: Map<QueryKey, CachedQuery<FunctionReference<"query">>>;
57
+ subs: Map<SubKey, SubEntry<FunctionReference<"query">>>;
58
+ convex: ConvexReactClient;
59
+ timeout: number;
60
+ maxIdleEntries: number;
61
+ idle: number;
62
+ constructor(convex: ConvexReactClient, options: ConvexQueryCacheOptions);
63
+ probe<Query extends FunctionReference<"query">>(queryKey: QueryKey): FunctionReturnType<Query> | undefined;
64
+ start<Query extends FunctionReference<"query">>(id: string, queryKey: string, query: Query, args: FunctionArgs<Query>, setter: (v: FunctionReturnType<Query>) => void): void;
65
+ end(id: string): void;
66
+ debug(): void;
67
+ }
68
+ export {};
69
+ //# sourceMappingURL=provider.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../react/cache/provider.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAa,iBAAiB,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EACL,YAAY,EACZ,iBAAiB,EACjB,kBAAkB,EACnB,MAAM,eAAe,CAAC;AACvB,OAAO,EAAiB,EAAE,EAAE,iBAAiB,EAAE,MAAM,OAAO,CAAC;AAE7D,eAAO,MAAM,uBAAuB;;EAElC,CAAC;AAEH;;;;;;;GAOG;AACH,eAAO,MAAM,wBAAwB,EAAE,EAAE,CACvC,iBAAiB,CAAC,uBAAuB,CAAC,CAgB3C,CAAC;AAKF,MAAM,MAAM,uBAAuB,GAAG;IACpC;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB;;;;;OAKG;IACH,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,CAAC;AAEF;;GAEG;AAEH,KAAK,MAAM,GAAG,MAAM,CAAC;AACrB,KAAK,QAAQ,GAAG,MAAM,CAAC;AACvB,KAAK,WAAW,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC,IAAI;IAC3D,IAAI,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAClB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,CAAC,EAAE,MAAM,IAAI,CAAC;IACnB,KAAK,CAAC,EAAE,kBAAkB,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC;CAC3C,CAAC;AACF,KAAK,QAAQ,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC,IAAI;IACxD,QAAQ,EAAE,QAAQ,CAAC;IACnB,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,kBAAkB,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC;CACjD,CAAC;AAGF,cAAM,aAAa;;IACjB,OAAO,EAAE,GAAG,CAAC,QAAQ,EAAE,WAAW,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IAChE,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,QAAQ,CAAC,iBAAiB,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;IACxD,MAAM,EAAE,iBAAiB,CAAC;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,cAAc,EAAE,MAAM,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;gBAED,MAAM,EAAE,iBAAiB,EAAE,OAAO,EAAE,uBAAuB;IA0BvE,KAAK,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC,EAC5C,QAAQ,EAAE,QAAQ,GACjB,kBAAkB,CAAC,KAAK,CAAC,GAAG,SAAS;IAMxC,KAAK,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC,EAC5C,EAAE,EAAE,MAAM,EACV,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,KAAK,EACZ,IAAI,EAAE,YAAY,CAAC,KAAK,CAAC,EACzB,MAAM,EAAE,CAAC,CAAC,EAAE,kBAAkB,CAAC,KAAK,CAAC,KAAK,IAAI,GAC7C,IAAI;IAoCP,GAAG,CAAC,EAAE,EAAE,MAAM;IA0Bd,KAAK;CAeN"}
@@ -0,0 +1,142 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useConvex } from "convex/react";
3
+ import { createContext } from "react";
4
+ export const ConvexQueryCacheContext = createContext({
5
+ registry: null,
6
+ });
7
+ /**
8
+ * A provider that establishes a query cache context in the React render
9
+ * tree so that cached `useQuery` calls can be used.
10
+ *
11
+ * @component
12
+ * @param {ConvexQueryCacheOptions} props.options - Options for the query cache
13
+ * @returns {Element}
14
+ */
15
+ export const ConvexQueryCacheProvider = ({ children, ...options }) => {
16
+ const convex = useConvex();
17
+ if (convex === undefined) {
18
+ throw new Error("Could not find Convex client! `ConvexQueryCacheProvider` must be used in the React component " +
19
+ "tree under `ConvexProvider`. Did you forget it? " +
20
+ "See https://docs.convex.dev/quick-start#set-up-convex-in-your-react-app");
21
+ }
22
+ const registry = new CacheRegistry(convex, options);
23
+ return (_jsx(ConvexQueryCacheContext.Provider, { value: { registry }, children: children }));
24
+ };
25
+ const DEFAULT_EXPIRATION_MS = 300_000; // 5 minutes
26
+ const DEFAULT_MAX_ENTRIES = 250;
27
+ // Core caching structure.
28
+ class CacheRegistry {
29
+ queries;
30
+ subs;
31
+ convex;
32
+ timeout;
33
+ maxIdleEntries;
34
+ idle;
35
+ constructor(convex, options) {
36
+ this.queries = new Map();
37
+ this.subs = new Map();
38
+ this.convex = convex;
39
+ this.idle = 0;
40
+ this.timeout = options.expiration ?? DEFAULT_EXPIRATION_MS;
41
+ this.maxIdleEntries = options.maxIdleEntries ?? DEFAULT_MAX_ENTRIES;
42
+ if (options.debug ?? false) {
43
+ const weakThis = new WeakRef(this);
44
+ const debugInterval = setInterval(() => {
45
+ const r = weakThis.deref();
46
+ if (r === undefined) {
47
+ clearInterval(debugInterval);
48
+ }
49
+ else {
50
+ r.debug();
51
+ }
52
+ }, 3000);
53
+ }
54
+ }
55
+ #getQueryEntry(queryKey) {
56
+ const entry = this.queries.get(queryKey);
57
+ return entry;
58
+ }
59
+ probe(queryKey) {
60
+ const entry = this.#getQueryEntry(queryKey);
61
+ return entry === undefined ? undefined : entry.value;
62
+ }
63
+ // Enable a new subscription.
64
+ start(id, queryKey, query, args, setter) {
65
+ let entry = this.#getQueryEntry(queryKey);
66
+ this.subs.set(id, {
67
+ queryKey,
68
+ setter,
69
+ });
70
+ if (entry === undefined) {
71
+ entry = {
72
+ refs: new Set(),
73
+ evictTimer: null,
74
+ };
75
+ const w = this.convex.watchQuery(query, args);
76
+ const unsub = w.onUpdate(() => {
77
+ const e = entry;
78
+ try {
79
+ e.value = w.localQueryResult();
80
+ }
81
+ catch (err) {
82
+ e.value = err;
83
+ }
84
+ for (const ref of e.refs.values()) {
85
+ this.subs.get(ref)?.setter(e.value);
86
+ }
87
+ });
88
+ entry.unsub = unsub;
89
+ this.queries.set(queryKey, entry);
90
+ }
91
+ else if (entry.evictTimer !== null) {
92
+ this.idle -= 1;
93
+ clearTimeout(entry.evictTimer);
94
+ entry.evictTimer = null;
95
+ }
96
+ entry.refs.add(id);
97
+ if (entry.value !== undefined) {
98
+ setter(entry.value);
99
+ }
100
+ }
101
+ // End a previous subscription.
102
+ end(id) {
103
+ const sub = this.subs.get(id);
104
+ if (sub) {
105
+ this.subs.delete(id);
106
+ const cq = this.queries.get(sub.queryKey);
107
+ const qk = sub.queryKey;
108
+ cq?.refs.delete(id);
109
+ // None left?
110
+ if (cq?.refs.size === 0) {
111
+ const remove = () => {
112
+ cq.unsub();
113
+ this.queries.delete(qk);
114
+ };
115
+ if (this.idle == this.maxIdleEntries) {
116
+ remove();
117
+ }
118
+ else {
119
+ this.idle += 1;
120
+ const evictTimer = window.setTimeout(() => {
121
+ this.idle -= 1;
122
+ remove();
123
+ }, this.timeout);
124
+ cq.evictTimer = evictTimer;
125
+ }
126
+ }
127
+ }
128
+ }
129
+ debug() {
130
+ console.log("DEBUG CACHE");
131
+ console.log(`IDLE = ${this.idle}`);
132
+ console.log(" SUBS");
133
+ for (const [k, v] of this.subs.entries()) {
134
+ console.log(` ${k} => ${v.queryKey}`);
135
+ }
136
+ console.log(" QUERIES");
137
+ for (const [k, v] of this.queries.entries()) {
138
+ console.log(` ${k} => ${v.refs.size} refs, evict = ${v.evictTimer}, value = ${v.value}`);
139
+ }
140
+ console.log("~~~~~~~~~~~~~~~~~~~~~~");
141
+ }
142
+ }
@@ -1,9 +1,73 @@
1
- import { OptionalRestArgsOrSkip } from "convex/react";
1
+ import { OptionalRestArgsOrSkip, useQueries } from "convex/react";
2
2
  import { FunctionReference, FunctionReturnType } from "convex/server";
3
3
  /**
4
4
  * Use in place of `useQuery` from "convex/react" to fetch data from a query
5
5
  * function but instead returns `{ status, data, error, isSuccess, isPending, isError}`.
6
6
  *
7
+ * Want a different name? Use `makeUseQueryWithStatus` to create a custom hook:
8
+ * ```ts
9
+ * import { useQueries } from "convex/react";
10
+ * import { makeUseQueryWithStatus } from "convex-helpers/react";
11
+ * export const useQuery = makeUseQueryWithStatus(useQueries);
12
+ * ```
13
+ *
14
+ * Status is one of "success", "pending", or "error".
15
+ * Docs copied from {@link useQueryOriginal} until `returns` block:
16
+ *
17
+ * Load a reactive query within a React component.
18
+ *
19
+ * This React hook contains internal state that will cause a rerender
20
+ * whenever the query result changes.
21
+ *
22
+ * Throws an error if not used under {@link ConvexProvider}.
23
+ *
24
+ * @param query - a {@link server.FunctionReference} for the public query to run
25
+ * like `api.dir1.dir2.filename.func`.
26
+ * @param args - The arguments to the query function or the string "skip" if the
27
+ * query should not be loaded.
28
+ * @returns {status, data, error, isSuccess, isPending, isError} where:
29
+ * - `status` is one of "success", "pending", or "error"
30
+ * - `data` is the result of the query function, if it loaded successfully,
31
+ * - `error` is an `Error` if the query threw an exception.
32
+ * - `isSuccess` is `true` if the query loaded successfully.
33
+ * - `isPending` is `true` if the query is still loading or "skip" was passed.
34
+ * - `isError` is `true` if the query threw an exception.
35
+ */
36
+ export declare const useQuery: <Query extends FunctionReference<"query">>(query: Query, ...queryArgs: OptionalRestArgsOrSkip<Query>) => {
37
+ status: "success";
38
+ data: FunctionReturnType<Query>;
39
+ error: undefined;
40
+ isSuccess: true;
41
+ isPending: false;
42
+ isError: false;
43
+ } | {
44
+ status: "pending";
45
+ data: undefined;
46
+ error: undefined;
47
+ isSuccess: false;
48
+ isPending: true;
49
+ isError: false;
50
+ } | {
51
+ status: "error";
52
+ data: undefined;
53
+ error: Error;
54
+ isSuccess: false;
55
+ isPending: false;
56
+ isError: true;
57
+ };
58
+ /**
59
+ * Makes a hook to use in place of `useQuery` from "convex/react" to fetch data from a query
60
+ * function but instead returns `{ status, data, error, isSuccess, isPending, isError}`.
61
+ *
62
+ * You can pass in any hook that matches the signature of {@link useQueries} from "convex/react".
63
+ * For instance:
64
+ *
65
+ * ```ts
66
+ * import { useQueries } from "convex-helpers/react/cache/hooks";
67
+ * import { makeUseQueryWithStatus } from "convex-helpers/react";
68
+ * const useQuery = makeUseQueryWithStatus(useQueries);
69
+ * ```
70
+ *
7
71
  * Status is one of "success", "pending", or "error".
8
72
  * Docs copied from {@link useQueryOriginal} until `returns` block:
9
73
  *
@@ -25,8 +89,12 @@ import { FunctionReference, FunctionReturnType } from "convex/server";
25
89
  * - `isSuccess` is `true` if the query loaded successfully.
26
90
  * - `isPending` is `true` if the query is still loading or "skip" was passed.
27
91
  * - `isError` is `true` if the query threw an exception.
92
+ *
93
+ * @param useQueries Something matching the signature of {@link useQueries} from "convex/react".
94
+ * @returns
95
+ * @returns A useQuery function that returns an object with status, data, error, isSuccess, isPending, isError.
28
96
  */
29
- export declare function useQuery<Query extends FunctionReference<"query">>(query: Query, ...queryArgs: OptionalRestArgsOrSkip<Query>): {
97
+ export declare function makeUseQueryWithStatus(useQueriesHook: typeof useQueries): <Query extends FunctionReference<"query">>(query: Query, ...queryArgs: OptionalRestArgsOrSkip<Query>) => {
30
98
  status: "success";
31
99
  data: FunctionReturnType<Query>;
32
100
  error: undefined;
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EAGvB,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAEnB,MAAM,eAAe,CAAC;AAIvB;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,QAAQ,CAAC,KAAK,SAAS,iBAAiB,CAAC,OAAO,CAAC,EAC/D,KAAK,EAAE,KAAK,EACZ,GAAG,SAAS,EAAE,sBAAsB,CAAC,KAAK,CAAC,GAEzC;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAChC,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,KAAK,CAAC;IACjB,OAAO,EAAE,KAAK,CAAC;CAChB,GACD;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,KAAK,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,KAAK,CAAC;CAChB,GACD;IACE,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,KAAK,CAAC;IACjB,SAAS,EAAE,KAAK,CAAC;IACjB,OAAO,EAAE,IAAI,CAAC;CACf,CAsDJ"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../react/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,sBAAsB,EACtB,UAAU,EAEX,MAAM,cAAc,CAAC;AACtB,OAAO,EACL,iBAAiB,EACjB,kBAAkB,EAEnB,MAAM,eAAe,CAAC;AAIvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,eAAO,MAAM,QAAQ,oDA2CV,KAAK,gBACE,uBAAuB,KAAK,CAAC,KAEzC;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,mBAAmB,KAAK,CAAC,CAAC;IAChC,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,KAAK,CAAC;IACjB,OAAO,EAAE,KAAK,CAAC;CAChB,GACD;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,KAAK,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,KAAK,CAAC;CAChB,GACD;IACE,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,KAAK,CAAC;IACjB,SAAS,EAAE,KAAK,CAAC;IACjB,OAAO,EAAE,IAAI,CAAC;CACf,AArEmD,CAAC;AAE3D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAsCG;AACH,wBAAgB,sBAAsB,CAAC,cAAc,EAAE,OAAO,UAAU,qDAE7D,KAAK,gBACE,uBAAuB,KAAK,CAAC,KAEzC;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,mBAAmB,KAAK,CAAC,CAAC;IAChC,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,SAAS,EAAE,KAAK,CAAC;IACjB,OAAO,EAAE,KAAK,CAAC;CAChB,GACD;IACE,MAAM,EAAE,SAAS,CAAC;IAClB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,SAAS,CAAC;IACjB,SAAS,EAAE,KAAK,CAAC;IACjB,SAAS,EAAE,IAAI,CAAC;IAChB,OAAO,EAAE,KAAK,CAAC;CAChB,GACD;IACE,MAAM,EAAE,OAAO,CAAC;IAChB,IAAI,EAAE,SAAS,CAAC;IAChB,KAAK,EAAE,KAAK,CAAC;IACb,SAAS,EAAE,KAAK,CAAC;IACjB,SAAS,EAAE,KAAK,CAAC;IACjB,OAAO,EAAE,IAAI,CAAC;CACf,CAuDN"}