@powersync/tanstack-react-query 0.1.9 → 0.1.10

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
@@ -106,6 +106,38 @@ export const TodoListDisplaySuspense = () => {
106
106
  };
107
107
  ```
108
108
 
109
+ ### useQueries
110
+
111
+ The `useQueries` hook allows you to run multiple queries in parallel and combine the results into a single result.
112
+
113
+ ```JSX
114
+ // TodoListDisplay.jsx
115
+ import { useQueries } from '@powersync/tanstack-react-query';
116
+
117
+ export const TodoListDisplay = () => {
118
+ const { data: todoLists } = useQueries({
119
+ queries: [
120
+ { queryKey: ['todoLists'], query: 'SELECT * from lists' },
121
+ { queryKey: ['todoLists2'], query: 'SELECT * from lists2' },
122
+ ],
123
+ combine: (results) => {
124
+ return {
125
+ data: results.map((result) => result.data),
126
+ pending: results.some((result) => result.isPending),
127
+ }
128
+ },
129
+ });
130
+
131
+ return (
132
+ <div>
133
+ {todoLists.map((list) => (
134
+ <li key={list.id}>{list.name}</li>
135
+ ))}
136
+ </div>
137
+ );
138
+ };
139
+ ```
140
+
109
141
  ### TypeScript Support
110
142
 
111
143
  A type can be specified for each row returned by `useQuery` and `useSuspenseQuery`.
@@ -0,0 +1,15 @@
1
+ import { type CompilableQuery } from '@powersync/common';
2
+ import * as Tanstack from '@tanstack/react-query';
3
+ export type UsePowerSyncQueriesInput = {
4
+ query?: string | CompilableQuery<unknown>;
5
+ parameters?: unknown[];
6
+ queryKey: Tanstack.QueryKey;
7
+ }[];
8
+ export type UsePowerSyncQueriesOutput = {
9
+ sqlStatement: string;
10
+ queryParameters: unknown[];
11
+ tables: string[];
12
+ error?: Error;
13
+ queryFn: () => Promise<unknown[]>;
14
+ }[];
15
+ export declare function usePowerSyncQueries(queries: UsePowerSyncQueriesInput, queryClient: Tanstack.QueryClient): UsePowerSyncQueriesOutput;
@@ -0,0 +1,151 @@
1
+ import { parseQuery } from '@powersync/common';
2
+ import { usePowerSync } from '@powersync/react';
3
+ import { useEffect, useState, useCallback, useMemo } from 'react';
4
+ export function usePowerSyncQueries(queries, queryClient) {
5
+ const powerSync = usePowerSync();
6
+ const [tablesArr, setTablesArr] = useState(() => queries.map(() => []));
7
+ const [errorsArr, setErrorsArr] = useState(() => queries.map(() => undefined));
8
+ const updateTablesArr = useCallback((tables, idx) => {
9
+ setTablesArr((prev) => {
10
+ if (JSON.stringify(prev[idx]) === JSON.stringify(tables))
11
+ return prev;
12
+ const next = [...prev];
13
+ next[idx] = tables;
14
+ return next;
15
+ });
16
+ }, []);
17
+ const updateErrorsArr = useCallback((error, idx) => {
18
+ setErrorsArr((prev) => {
19
+ if (prev[idx]?.message === error?.message)
20
+ return prev;
21
+ const next = [...prev];
22
+ next[idx] = error;
23
+ return next;
24
+ });
25
+ }, []);
26
+ const parsedQueries = useMemo(() => queries.map((queryInput) => {
27
+ const { query, parameters = [], queryKey } = queryInput;
28
+ if (!query) {
29
+ return {
30
+ query,
31
+ parameters,
32
+ queryKey,
33
+ sqlStatement: '',
34
+ queryParameters: [],
35
+ parseError: undefined
36
+ };
37
+ }
38
+ try {
39
+ const parsed = parseQuery(query, parameters);
40
+ return {
41
+ query,
42
+ parameters,
43
+ queryKey,
44
+ sqlStatement: parsed.sqlStatement,
45
+ queryParameters: parsed.parameters,
46
+ parseError: undefined
47
+ };
48
+ }
49
+ catch (e) {
50
+ return {
51
+ query,
52
+ parameters,
53
+ queryKey,
54
+ sqlStatement: '',
55
+ queryParameters: [],
56
+ parseError: e
57
+ };
58
+ }
59
+ }), [queries]);
60
+ useEffect(() => {
61
+ parsedQueries.forEach((pq, idx) => {
62
+ if (pq.parseError) {
63
+ updateErrorsArr(pq.parseError, idx);
64
+ }
65
+ });
66
+ }, [parsedQueries, updateErrorsArr]);
67
+ const stringifiedQueriesDeps = JSON.stringify(parsedQueries.map((q) => ({
68
+ sql: q.sqlStatement,
69
+ params: q.queryParameters
70
+ })));
71
+ useEffect(() => {
72
+ const listeners = parsedQueries.map((pq, idx) => {
73
+ if (pq.parseError || !pq.query) {
74
+ return null;
75
+ }
76
+ (async () => {
77
+ try {
78
+ const tables = await powerSync.resolveTables(pq.sqlStatement, pq.queryParameters);
79
+ updateTablesArr(tables, idx);
80
+ }
81
+ catch (e) {
82
+ updateErrorsArr(e, idx);
83
+ }
84
+ })();
85
+ return powerSync.registerListener({
86
+ schemaChanged: async () => {
87
+ try {
88
+ const tables = await powerSync.resolveTables(pq.sqlStatement, pq.queryParameters);
89
+ updateTablesArr(tables, idx);
90
+ queryClient.invalidateQueries({ queryKey: pq.queryKey });
91
+ }
92
+ catch (e) {
93
+ updateErrorsArr(e, idx);
94
+ }
95
+ }
96
+ });
97
+ });
98
+ return () => {
99
+ listeners.forEach((l) => l?.());
100
+ };
101
+ }, [powerSync, queryClient, stringifiedQueriesDeps, updateTablesArr, updateErrorsArr]);
102
+ const stringifiedQueryKeys = JSON.stringify(parsedQueries.map((q) => q.queryKey));
103
+ useEffect(() => {
104
+ const aborts = parsedQueries.map((pq, idx) => {
105
+ if (pq.parseError || !pq.query) {
106
+ return null;
107
+ }
108
+ const abort = new AbortController();
109
+ powerSync.onChangeWithCallback({
110
+ onChange: () => {
111
+ queryClient.invalidateQueries({ queryKey: pq.queryKey });
112
+ },
113
+ onError: (e) => {
114
+ updateErrorsArr(e, idx);
115
+ }
116
+ }, {
117
+ tables: tablesArr[idx],
118
+ signal: abort.signal
119
+ });
120
+ return abort;
121
+ });
122
+ return () => aborts.forEach((a) => a?.abort());
123
+ }, [powerSync, queryClient, tablesArr, updateErrorsArr, stringifiedQueryKeys]);
124
+ return useMemo(() => {
125
+ return parsedQueries.map((pq, idx) => {
126
+ const error = errorsArr[idx] || pq.parseError;
127
+ const queryFn = async () => {
128
+ if (error)
129
+ throw error;
130
+ if (!pq.query)
131
+ throw new Error('No query provided');
132
+ try {
133
+ return typeof pq.query === 'string'
134
+ ? await powerSync.getAll(pq.sqlStatement, pq.queryParameters)
135
+ : await pq.query.execute();
136
+ }
137
+ catch (e) {
138
+ throw e;
139
+ }
140
+ };
141
+ return {
142
+ sqlStatement: pq.sqlStatement,
143
+ queryParameters: pq.queryParameters,
144
+ tables: tablesArr[idx],
145
+ error,
146
+ queryFn
147
+ };
148
+ });
149
+ }, [parsedQueries, errorsArr, tablesArr, powerSync]);
150
+ }
151
+ //# sourceMappingURL=usePowerSyncQueries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"usePowerSyncQueries.js","sourceRoot":"","sources":["../../src/hooks/usePowerSyncQueries.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,59 @@
1
+ import { type CompilableQuery } from '@powersync/common';
2
+ import * as Tanstack from '@tanstack/react-query';
3
+ export type PowerSyncQueryOptions<T> = {
4
+ query?: string | CompilableQuery<T>;
5
+ parameters?: any[];
6
+ };
7
+ export type PowerSyncQueryOption<T = unknown[]> = Tanstack.UseQueryOptions<T[]> & PowerSyncQueryOptions<T>;
8
+ export type InferQueryResults<TQueries extends readonly unknown[]> = {
9
+ [K in keyof TQueries]: TQueries[K] extends {
10
+ query: CompilableQuery<infer TData>;
11
+ } ? Tanstack.UseQueryResult<TData[]> : Tanstack.UseQueryResult<unknown[]>;
12
+ };
13
+ export type ExplicitQueryResults<T extends readonly unknown[]> = {
14
+ [K in keyof T]: Tanstack.UseQueryResult<T[K][]>;
15
+ };
16
+ export type EnhancedInferQueryResults<TQueries extends readonly unknown[]> = {
17
+ [K in keyof TQueries]: TQueries[K] extends {
18
+ query: CompilableQuery<infer TData>;
19
+ } ? Tanstack.UseQueryResult<TData[]> & {
20
+ queryKey: Tanstack.QueryKey;
21
+ } : Tanstack.UseQueryResult<unknown[]> & {
22
+ queryKey: Tanstack.QueryKey;
23
+ };
24
+ };
25
+ export type EnhancedExplicitQueryResults<T extends readonly unknown[]> = {
26
+ [K in keyof T]: Tanstack.UseQueryResult<T[K][]> & {
27
+ queryKey: Tanstack.QueryKey;
28
+ };
29
+ };
30
+ export type UseQueriesExplicitWithCombineOptions<T extends readonly unknown[], TCombined> = {
31
+ queries: readonly [...{
32
+ [K in keyof T]: PowerSyncQueryOption<T[K]>;
33
+ }];
34
+ combine: (results: EnhancedExplicitQueryResults<T>) => TCombined;
35
+ };
36
+ export type UseQueriesExplicitWithoutCombineOptions<T extends readonly unknown[]> = {
37
+ queries: readonly [...{
38
+ [K in keyof T]: PowerSyncQueryOption<T[K]>;
39
+ }];
40
+ combine?: undefined;
41
+ };
42
+ export type UseQueriesAutoInferenceWithCombineOptions<TQueries extends readonly PowerSyncQueryOption[], TCombined> = {
43
+ queries: readonly [...TQueries];
44
+ combine: (results: EnhancedInferQueryResults<TQueries>) => TCombined;
45
+ };
46
+ export type UseQueriesAutoInferenceWithoutCombineOptions<TQueries extends readonly PowerSyncQueryOption[]> = {
47
+ queries: readonly [...TQueries];
48
+ combine?: undefined;
49
+ };
50
+ export type UseQueriesBaseOptions = {
51
+ queries: readonly (Tanstack.UseQueryOptions & PowerSyncQueryOptions<unknown>)[];
52
+ combine?: (results: (Tanstack.UseQueryResult<unknown, unknown> & {
53
+ queryKey: Tanstack.QueryKey;
54
+ })[]) => unknown;
55
+ };
56
+ export declare function useQueries<T extends readonly unknown[], TCombined>(options: UseQueriesExplicitWithCombineOptions<T, TCombined>, queryClient?: Tanstack.QueryClient): TCombined;
57
+ export declare function useQueries<T extends readonly unknown[]>(options: UseQueriesExplicitWithoutCombineOptions<T>, queryClient?: Tanstack.QueryClient): ExplicitQueryResults<T>;
58
+ export declare function useQueries<TQueries extends readonly PowerSyncQueryOption[], TCombined>(options: UseQueriesAutoInferenceWithCombineOptions<TQueries, TCombined>, queryClient?: Tanstack.QueryClient): TCombined;
59
+ export declare function useQueries<TQueries extends readonly PowerSyncQueryOption[]>(options: UseQueriesAutoInferenceWithoutCombineOptions<TQueries>, queryClient?: Tanstack.QueryClient): InferQueryResults<TQueries>;
@@ -0,0 +1,70 @@
1
+ import { usePowerSync } from '@powersync/react';
2
+ import * as Tanstack from '@tanstack/react-query';
3
+ import { useMemo } from 'react';
4
+ import { usePowerSyncQueries } from './usePowerSyncQueries.js';
5
+ /**
6
+ * @example
7
+ * ```
8
+ * const { data, error, isLoading } = useQueries({
9
+ * queries: [
10
+ * { queryKey: ['lists'], query: 'SELECT * from lists' },
11
+ * { queryKey: ['todos'], query: 'SELECT * from todos' }
12
+ * ],
13
+ * })
14
+ * ```
15
+ *
16
+ * @example
17
+ * ```
18
+ * const ids = [1, 2, 3];
19
+ * const combinedQueries = useQueries({
20
+ * queries: ids.map((id) => ({
21
+ * queryKey: ['post', id],
22
+ * query: 'SELECT * from lists where id = ?',
23
+ * parameters: [id],
24
+ * })),
25
+ * combine: (results) => {
26
+ * return {
27
+ * data: results.map((result) => result.data),
28
+ * pending: results.some((result) => result.isPending),
29
+ * }
30
+ * },
31
+ * });
32
+ * ```
33
+ */
34
+ export function useQueries(options, queryClient = Tanstack.useQueryClient()) {
35
+ const powerSync = usePowerSync();
36
+ if (!powerSync) {
37
+ throw new Error('PowerSync is not available');
38
+ }
39
+ const queriesInput = options.queries;
40
+ const powerSyncQueriesInput = useMemo(() => queriesInput.map((queryOptions) => ({
41
+ query: queryOptions.query,
42
+ parameters: queryOptions.parameters,
43
+ queryKey: queryOptions.queryKey
44
+ })), [queriesInput]);
45
+ const states = usePowerSyncQueries(powerSyncQueriesInput, queryClient);
46
+ const queries = useMemo(() => {
47
+ return queriesInput.map((queryOptions, idx) => {
48
+ const { query, parameters, ...rest } = queryOptions;
49
+ const state = states[idx];
50
+ return {
51
+ ...rest,
52
+ queryFn: query ? state.queryFn : rest.queryFn,
53
+ queryKey: rest.queryKey
54
+ };
55
+ });
56
+ }, [queriesInput, states]);
57
+ return Tanstack.useQueries({
58
+ queries: queries,
59
+ combine: options.combine
60
+ ? (results) => {
61
+ const enhancedResultsWithQueryKey = results.map((result, index) => ({
62
+ ...result,
63
+ queryKey: queries[index].queryKey
64
+ }));
65
+ return options.combine?.(enhancedResultsWithQueryKey);
66
+ }
67
+ : undefined
68
+ }, queryClient);
69
+ }
70
+ //# sourceMappingURL=useQueries.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"useQueries.js","sourceRoot":"","sources":["../../src/hooks/useQueries.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,KAAK,QAAQ,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,OAAO,EAAE,MAAM,OAAO,CAAC;AAChC,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AA8E/D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,MAAM,UAAU,UAAU,CACxB,OAA8B,EAC9B,cAAoC,QAAQ,CAAC,cAAc,EAAE;IAE7D,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IAEjC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC;IAErC,MAAM,qBAAqB,GAAG,OAAO,CACnC,GAAG,EAAE,CACH,YAAY,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;QAClC,KAAK,EAAE,YAAY,CAAC,KAAK;QACzB,UAAU,EAAE,YAAY,CAAC,UAAU;QACnC,QAAQ,EAAE,YAAY,CAAC,QAAQ;KAChC,CAAC,CAAC,EACL,CAAC,YAAY,CAAC,CACf,CAAC;IAEF,MAAM,MAAM,GAAG,mBAAmB,CAAC,qBAAqB,EAAE,WAAW,CAAC,CAAC;IAEvE,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,EAAE;QAC3B,OAAO,YAAY,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,GAAG,EAAE,EAAE;YAC5C,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,YAAY,CAAC;YACpD,MAAM,KAAK,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;YAE1B,OAAO;gBACL,GAAG,IAAI;gBACP,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,OAAO;gBAC7C,QAAQ,EAAE,IAAI,CAAC,QAAQ;aACxB,CAAC;QACJ,CAAC,CAAC,CAAC;IACL,CAAC,EAAE,CAAC,YAAY,EAAE,MAAM,CAAC,CAAC,CAAC;IAE3B,OAAO,QAAQ,CAAC,UAAU,CACxB;QACE,OAAO,EAAE,OAAuC;QAChD,OAAO,EAAE,OAAO,CAAC,OAAO;YACtB,CAAC,CAAC,CAAC,OAAO,EAAE,EAAE;gBACV,MAAM,2BAA2B,GAAG,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC;oBAClE,GAAG,MAAM;oBACT,QAAQ,EAAE,OAAO,CAAC,KAAK,CAAC,CAAC,QAAQ;iBAClC,CAAC,CAAC,CAAC;gBAEJ,OAAO,OAAO,CAAC,OAAO,EAAE,CAAC,2BAA2B,CAAC,CAAC;YACxD,CAAC;YACH,CAAC,CAAC,SAAS;KACd,EACD,WAAW,CACZ,CAAC;AACJ,CAAC"}
@@ -5,15 +5,73 @@ export type PowerSyncQueryOptions<T> = {
5
5
  parameters?: any[];
6
6
  };
7
7
  export type UseBaseQueryOptions<TQueryOptions> = TQueryOptions & PowerSyncQueryOptions<any>;
8
+ /**
9
+ *
10
+ * Uses the `queryFn` to execute the query. No different from the base `useQuery` hook.
11
+ *
12
+ * @example
13
+ * ```
14
+ * const { data, error, isLoading } = useQuery({
15
+ * queryKey: ['lists'],
16
+ * queryFn: getTodos,
17
+ * });
18
+ * ```
19
+ */
8
20
  export declare function useQuery<TData = unknown, TError = Tanstack.DefaultError>(options: UseBaseQueryOptions<Tanstack.UseQueryOptions<TData, TError>> & {
9
21
  query?: undefined;
10
22
  }, queryClient?: Tanstack.QueryClient): Tanstack.UseQueryResult<TData, TError>;
23
+ /**
24
+ *
25
+ * Uses the `query` to execute the PowerSync query.
26
+ *
27
+ * @example
28
+ * ```
29
+ * const { data, error, isLoading } = useQuery({
30
+ * queryKey: ['lists'],
31
+ * query: 'SELECT * from lists where id = ?',
32
+ * parameters: ['id-1']
33
+ * });
34
+ * ```
35
+ *
36
+ * @example
37
+ * ```
38
+ * const { data, error, isLoading } = useQuery({
39
+ * queryKey: ['lists'],
40
+ * query: compilableQuery,
41
+ * });
42
+ * ```
43
+ */
11
44
  export declare function useQuery<TData = unknown, TError = Tanstack.DefaultError>(options: UseBaseQueryOptions<Tanstack.UseQueryOptions<TData[], TError>> & {
12
45
  query: string | CompilableQuery<TData>;
13
46
  }, queryClient?: Tanstack.QueryClient): Tanstack.UseQueryResult<TData[], TError>;
47
+ /**
48
+ *
49
+ * Uses the `queryFn` to execute the query. No different from the base `useSuspenseQuery` hook.
50
+ *
51
+ * @example
52
+ * ```
53
+ * const { data } = useSuspenseQuery({
54
+ * queryKey: ['lists'],
55
+ * queryFn: getTodos,
56
+ * });
57
+ * ```
58
+ */
14
59
  export declare function useSuspenseQuery<TData = unknown, TError = Tanstack.DefaultError>(options: UseBaseQueryOptions<Tanstack.UseSuspenseQueryOptions<TData, TError>> & {
15
60
  query?: undefined;
16
61
  }, queryClient?: Tanstack.QueryClient): Tanstack.UseSuspenseQueryResult<TData, TError>;
62
+ /***
63
+ *
64
+ * Uses the `query` to execute the PowerSync query.
65
+ *
66
+ * @example
67
+ * ```
68
+ * const { data } = useSuspenseQuery({
69
+ * queryKey: ['lists'],
70
+ * query: 'SELECT * from lists where id = ?',
71
+ * parameters: ['id-1']
72
+ * });
73
+ * ```
74
+ */
17
75
  export declare function useSuspenseQuery<TData = unknown, TError = Tanstack.DefaultError>(options: UseBaseQueryOptions<Tanstack.UseSuspenseQueryOptions<TData[], TError>> & {
18
76
  query: string | CompilableQuery<TData>;
19
77
  }, queryClient?: Tanstack.QueryClient): Tanstack.UseSuspenseQueryResult<TData[], TError>;
@@ -1,7 +1,6 @@
1
- import { parseQuery } from '@powersync/common';
2
1
  import { usePowerSync } from '@powersync/react';
3
- import React from 'react';
4
2
  import * as Tanstack from '@tanstack/react-query';
3
+ import { usePowerSyncQueries } from './usePowerSyncQueries.js';
5
4
  export function useQuery(options, queryClient = Tanstack.useQueryClient()) {
6
5
  return useQueryCore(options, queryClient, Tanstack.useQuery);
7
6
  }
@@ -13,78 +12,17 @@ function useQueryCore(options, queryClient, useQueryFn) {
13
12
  if (!powerSync) {
14
13
  throw new Error('PowerSync is not available');
15
14
  }
16
- let error = undefined;
17
- const [tables, setTables] = React.useState([]);
18
- const { query, parameters = [], ...resolvedOptions } = options;
19
- let sqlStatement = '';
20
- let queryParameters = [];
21
- if (query) {
22
- try {
23
- const parsedQuery = parseQuery(query, parameters);
24
- sqlStatement = parsedQuery.sqlStatement;
25
- queryParameters = parsedQuery.parameters;
15
+ const { query, parameters, queryKey, ...resolvedOptions } = options;
16
+ const [{ queryFn }] = usePowerSyncQueries([
17
+ {
18
+ query,
19
+ parameters,
20
+ queryKey
26
21
  }
27
- catch (e) {
28
- error = e;
29
- }
30
- }
31
- const stringifiedParams = JSON.stringify(queryParameters);
32
- const stringifiedKey = JSON.stringify(options.queryKey);
33
- const fetchTables = async () => {
34
- try {
35
- const tables = await powerSync.resolveTables(sqlStatement, queryParameters);
36
- setTables(tables);
37
- }
38
- catch (e) {
39
- error = e;
40
- }
41
- };
42
- React.useEffect(() => {
43
- if (error || !query)
44
- return () => { };
45
- (async () => {
46
- await fetchTables();
47
- })();
48
- const l = powerSync.registerListener({
49
- schemaChanged: async () => {
50
- await fetchTables();
51
- queryClient.invalidateQueries({ queryKey: options.queryKey });
52
- }
53
- });
54
- return () => l?.();
55
- }, [powerSync, sqlStatement, stringifiedParams]);
56
- const queryFn = React.useCallback(async () => {
57
- if (error) {
58
- return Promise.reject(error);
59
- }
60
- try {
61
- return typeof query == 'string' ? powerSync.getAll(sqlStatement, queryParameters) : query.execute();
62
- }
63
- catch (e) {
64
- return Promise.reject(e);
65
- }
66
- }, [powerSync, query, parameters, stringifiedKey]);
67
- React.useEffect(() => {
68
- if (error || !query)
69
- return () => { };
70
- const abort = new AbortController();
71
- powerSync.onChangeWithCallback({
72
- onChange: () => {
73
- queryClient.invalidateQueries({
74
- queryKey: options.queryKey
75
- });
76
- },
77
- onError: (e) => {
78
- error = e;
79
- }
80
- }, {
81
- tables,
82
- signal: abort.signal
83
- });
84
- return () => abort.abort();
85
- }, [powerSync, queryClient, stringifiedKey, tables]);
22
+ ], queryClient);
86
23
  return useQueryFn({
87
24
  ...resolvedOptions,
25
+ queryKey,
88
26
  queryFn: query ? queryFn : resolvedOptions.queryFn
89
27
  }, queryClient);
90
28
  }
@@ -1 +1 @@
1
- {"version":3,"file":"useQuery.js","sourceRoot":"","sources":["../../src/hooks/useQuery.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"useQuery.js","sourceRoot":"","sources":["../../src/hooks/useQuery.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAChD,OAAO,KAAK,QAAQ,MAAM,uBAAuB,CAAC;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,0BAA0B,CAAC;AAoD/D,MAAM,UAAU,QAAQ,CACtB,OAAqE,EACrE,cAAoC,QAAQ,CAAC,cAAc,EAAE;IAE7D,OAAO,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAC/D,CAAC;AAuCD,MAAM,UAAU,gBAAgB,CAC9B,OAA6E,EAC7E,cAAoC,QAAQ,CAAC,cAAc,EAAE;IAE7D,OAAO,YAAY,CAAC,OAAO,EAAE,WAAW,EAAE,QAAQ,CAAC,gBAAgB,CAAC,CAAC;AACvE,CAAC;AAED,SAAS,YAAY,CAMnB,OAA2C,EAC3C,WAAiC,EACjC,UAAwF;IAExF,MAAM,SAAS,GAAG,YAAY,EAAE,CAAC;IAEjC,IAAI,CAAC,SAAS,EAAE,CAAC;QACf,MAAM,IAAI,KAAK,CAAC,4BAA4B,CAAC,CAAC;IAChD,CAAC;IAED,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,QAAQ,EAAE,GAAG,eAAe,EAAE,GAAG,OAAO,CAAC;IAEpE,MAAM,CAAC,EAAE,OAAO,EAAE,CAAC,GAAG,mBAAmB,CACvC;QACE;YACE,KAAK;YACL,UAAU;YACV,QAAQ;SACT;KACF,EACD,WAAW,CACZ,CAAC;IAEF,OAAO,UAAU,CACf;QACE,GAAI,eAAiC;QACrC,QAAQ;QACR,OAAO,EAAE,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,eAAe,CAAC,OAAO;KAClC,EAClB,WAAW,CACZ,CAAC;AACJ,CAAC"}
package/lib/index.d.ts CHANGED
@@ -1 +1,2 @@
1
1
  export { useQuery, useSuspenseQuery } from './hooks/useQuery.js';
2
+ export { useQueries } from './hooks/useQueries.js';
package/lib/index.js CHANGED
@@ -1,2 +1,3 @@
1
1
  export { useQuery, useSuspenseQuery } from './hooks/useQuery.js';
2
+ export { useQueries } from './hooks/useQueries.js';
2
3
  //# sourceMappingURL=index.js.map
package/lib/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,uBAAuB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powersync/tanstack-react-query",
3
- "version": "0.1.9",
3
+ "version": "0.1.10",
4
4
  "publishConfig": {
5
5
  "registry": "https://registry.npmjs.org/",
6
6
  "access": "public"
@@ -24,12 +24,12 @@
24
24
  },
25
25
  "homepage": "https://docs.powersync.com",
26
26
  "peerDependencies": {
27
- "@powersync/common": "^1.42.0",
27
+ "@powersync/common": "^1.43.0",
28
28
  "react": "*"
29
29
  },
30
30
  "dependencies": {
31
31
  "@tanstack/react-query": "^5.70.0",
32
- "@powersync/common": "1.42.0",
32
+ "@powersync/common": "1.43.0",
33
33
  "@powersync/react": "1.8.2"
34
34
  },
35
35
  "devDependencies": {
@@ -0,0 +1,192 @@
1
+ import { type CompilableQuery, parseQuery } from '@powersync/common';
2
+ import { usePowerSync } from '@powersync/react';
3
+ import { useEffect, useState, useCallback, useMemo } from 'react';
4
+ import * as Tanstack from '@tanstack/react-query';
5
+
6
+ export type UsePowerSyncQueriesInput = {
7
+ query?: string | CompilableQuery<unknown>;
8
+ parameters?: unknown[];
9
+ queryKey: Tanstack.QueryKey;
10
+ }[];
11
+
12
+ export type UsePowerSyncQueriesOutput = {
13
+ sqlStatement: string;
14
+ queryParameters: unknown[];
15
+ tables: string[];
16
+ error?: Error;
17
+ queryFn: () => Promise<unknown[]>;
18
+ }[];
19
+
20
+ export function usePowerSyncQueries(
21
+ queries: UsePowerSyncQueriesInput,
22
+ queryClient: Tanstack.QueryClient
23
+ ): UsePowerSyncQueriesOutput {
24
+ const powerSync = usePowerSync();
25
+
26
+ const [tablesArr, setTablesArr] = useState<string[][]>(() => queries.map(() => []));
27
+ const [errorsArr, setErrorsArr] = useState<(Error | undefined)[]>(() => queries.map(() => undefined));
28
+
29
+ const updateTablesArr = useCallback((tables: string[], idx: number) => {
30
+ setTablesArr((prev) => {
31
+ if (JSON.stringify(prev[idx]) === JSON.stringify(tables)) return prev;
32
+ const next = [...prev];
33
+ next[idx] = tables;
34
+ return next;
35
+ });
36
+ }, []);
37
+
38
+ const updateErrorsArr = useCallback((error: Error | undefined, idx: number) => {
39
+ setErrorsArr((prev) => {
40
+ if (prev[idx]?.message === error?.message) return prev;
41
+ const next = [...prev];
42
+ next[idx] = error;
43
+ return next;
44
+ });
45
+ }, []);
46
+
47
+ const parsedQueries = useMemo(
48
+ () =>
49
+ queries.map((queryInput) => {
50
+ const { query, parameters = [], queryKey } = queryInput;
51
+
52
+ if (!query) {
53
+ return {
54
+ query,
55
+ parameters,
56
+ queryKey,
57
+ sqlStatement: '',
58
+ queryParameters: [],
59
+ parseError: undefined
60
+ };
61
+ }
62
+
63
+ try {
64
+ const parsed = parseQuery(query, parameters);
65
+ return {
66
+ query,
67
+ parameters,
68
+ queryKey,
69
+ sqlStatement: parsed.sqlStatement,
70
+ queryParameters: parsed.parameters,
71
+ parseError: undefined
72
+ };
73
+ } catch (e) {
74
+ return {
75
+ query,
76
+ parameters,
77
+ queryKey,
78
+ sqlStatement: '',
79
+ queryParameters: [],
80
+ parseError: e as Error
81
+ };
82
+ }
83
+ }),
84
+ [queries]
85
+ );
86
+
87
+ useEffect(() => {
88
+ parsedQueries.forEach((pq, idx) => {
89
+ if (pq.parseError) {
90
+ updateErrorsArr(pq.parseError, idx);
91
+ }
92
+ });
93
+ }, [parsedQueries, updateErrorsArr]);
94
+
95
+ const stringifiedQueriesDeps = JSON.stringify(
96
+ parsedQueries.map((q) => ({
97
+ sql: q.sqlStatement,
98
+ params: q.queryParameters
99
+ }))
100
+ );
101
+
102
+ useEffect(() => {
103
+ const listeners = parsedQueries.map((pq, idx) => {
104
+ if (pq.parseError || !pq.query) {
105
+ return null;
106
+ }
107
+
108
+ (async () => {
109
+ try {
110
+ const tables = await powerSync.resolveTables(pq.sqlStatement, pq.queryParameters);
111
+ updateTablesArr(tables, idx);
112
+ } catch (e) {
113
+ updateErrorsArr(e as Error, idx);
114
+ }
115
+ })();
116
+
117
+ return powerSync.registerListener({
118
+ schemaChanged: async () => {
119
+ try {
120
+ const tables = await powerSync.resolveTables(pq.sqlStatement, pq.queryParameters);
121
+ updateTablesArr(tables, idx);
122
+ queryClient.invalidateQueries({ queryKey: pq.queryKey });
123
+ } catch (e) {
124
+ updateErrorsArr(e as Error, idx);
125
+ }
126
+ }
127
+ });
128
+ });
129
+
130
+ return () => {
131
+ listeners.forEach((l) => l?.());
132
+ };
133
+ }, [powerSync, queryClient, stringifiedQueriesDeps, updateTablesArr, updateErrorsArr]);
134
+
135
+ const stringifiedQueryKeys = JSON.stringify(parsedQueries.map((q) => q.queryKey));
136
+
137
+ useEffect(() => {
138
+ const aborts = parsedQueries.map((pq, idx) => {
139
+ if (pq.parseError || !pq.query) {
140
+ return null;
141
+ }
142
+
143
+ const abort = new AbortController();
144
+
145
+ powerSync.onChangeWithCallback(
146
+ {
147
+ onChange: () => {
148
+ queryClient.invalidateQueries({ queryKey: pq.queryKey });
149
+ },
150
+ onError: (e) => {
151
+ updateErrorsArr(e, idx);
152
+ }
153
+ },
154
+ {
155
+ tables: tablesArr[idx],
156
+ signal: abort.signal
157
+ }
158
+ );
159
+
160
+ return abort;
161
+ });
162
+
163
+ return () => aborts.forEach((a) => a?.abort());
164
+ }, [powerSync, queryClient, tablesArr, updateErrorsArr, stringifiedQueryKeys]);
165
+
166
+ return useMemo(() => {
167
+ return parsedQueries.map((pq, idx) => {
168
+ const error = errorsArr[idx] || pq.parseError;
169
+
170
+ const queryFn = async () => {
171
+ if (error) throw error;
172
+ if (!pq.query) throw new Error('No query provided');
173
+
174
+ try {
175
+ return typeof pq.query === 'string'
176
+ ? await powerSync.getAll(pq.sqlStatement, pq.queryParameters)
177
+ : await pq.query.execute();
178
+ } catch (e) {
179
+ throw e;
180
+ }
181
+ };
182
+
183
+ return {
184
+ sqlStatement: pq.sqlStatement,
185
+ queryParameters: pq.queryParameters,
186
+ tables: tablesArr[idx],
187
+ error,
188
+ queryFn
189
+ };
190
+ });
191
+ }, [parsedQueries, errorsArr, tablesArr, powerSync]);
192
+ }
@@ -0,0 +1,165 @@
1
+ import { type CompilableQuery } from '@powersync/common';
2
+ import { usePowerSync } from '@powersync/react';
3
+ import * as Tanstack from '@tanstack/react-query';
4
+ import { useMemo } from 'react';
5
+ import { usePowerSyncQueries } from './usePowerSyncQueries.js';
6
+
7
+ export type PowerSyncQueryOptions<T> = {
8
+ query?: string | CompilableQuery<T>;
9
+ parameters?: any[];
10
+ };
11
+
12
+ export type PowerSyncQueryOption<T = unknown[]> = Tanstack.UseQueryOptions<T[]> & PowerSyncQueryOptions<T>;
13
+
14
+ export type InferQueryResults<TQueries extends readonly unknown[]> = {
15
+ [K in keyof TQueries]: TQueries[K] extends { query: CompilableQuery<infer TData> }
16
+ ? Tanstack.UseQueryResult<TData[]>
17
+ : Tanstack.UseQueryResult<unknown[]>;
18
+ };
19
+
20
+ export type ExplicitQueryResults<T extends readonly unknown[]> = {
21
+ [K in keyof T]: Tanstack.UseQueryResult<T[K][]>;
22
+ };
23
+
24
+ export type EnhancedInferQueryResults<TQueries extends readonly unknown[]> = {
25
+ [K in keyof TQueries]: TQueries[K] extends { query: CompilableQuery<infer TData> }
26
+ ? Tanstack.UseQueryResult<TData[]> & { queryKey: Tanstack.QueryKey }
27
+ : Tanstack.UseQueryResult<unknown[]> & { queryKey: Tanstack.QueryKey };
28
+ };
29
+
30
+ export type EnhancedExplicitQueryResults<T extends readonly unknown[]> = {
31
+ [K in keyof T]: Tanstack.UseQueryResult<T[K][]> & { queryKey: Tanstack.QueryKey };
32
+ };
33
+
34
+ export type UseQueriesExplicitWithCombineOptions<T extends readonly unknown[], TCombined> = {
35
+ queries: readonly [...{ [K in keyof T]: PowerSyncQueryOption<T[K]> }];
36
+ combine: (results: EnhancedExplicitQueryResults<T>) => TCombined;
37
+ };
38
+
39
+ export type UseQueriesExplicitWithoutCombineOptions<T extends readonly unknown[]> = {
40
+ queries: readonly [...{ [K in keyof T]: PowerSyncQueryOption<T[K]> }];
41
+ combine?: undefined;
42
+ };
43
+
44
+ export type UseQueriesAutoInferenceWithCombineOptions<TQueries extends readonly PowerSyncQueryOption[], TCombined> = {
45
+ queries: readonly [...TQueries];
46
+ combine: (results: EnhancedInferQueryResults<TQueries>) => TCombined;
47
+ };
48
+
49
+ export type UseQueriesAutoInferenceWithoutCombineOptions<TQueries extends readonly PowerSyncQueryOption[]> = {
50
+ queries: readonly [...TQueries];
51
+ combine?: undefined;
52
+ };
53
+
54
+ export type UseQueriesBaseOptions = {
55
+ queries: readonly (Tanstack.UseQueryOptions & PowerSyncQueryOptions<unknown>)[];
56
+ combine?: (results: (Tanstack.UseQueryResult<unknown, unknown> & { queryKey: Tanstack.QueryKey })[]) => unknown;
57
+ };
58
+
59
+ // Explicit generic typing with combine
60
+ export function useQueries<T extends readonly unknown[], TCombined>(
61
+ options: UseQueriesExplicitWithCombineOptions<T, TCombined>,
62
+ queryClient?: Tanstack.QueryClient
63
+ ): TCombined;
64
+
65
+ // Explicit generic typing without combine
66
+ export function useQueries<T extends readonly unknown[]>(
67
+ options: UseQueriesExplicitWithoutCombineOptions<T>,
68
+ queryClient?: Tanstack.QueryClient
69
+ ): ExplicitQueryResults<T>;
70
+
71
+ // Auto inference with combine
72
+ export function useQueries<TQueries extends readonly PowerSyncQueryOption[], TCombined>(
73
+ options: UseQueriesAutoInferenceWithCombineOptions<TQueries, TCombined>,
74
+ queryClient?: Tanstack.QueryClient
75
+ ): TCombined;
76
+
77
+ // Auto inference without combine
78
+ export function useQueries<TQueries extends readonly PowerSyncQueryOption[]>(
79
+ options: UseQueriesAutoInferenceWithoutCombineOptions<TQueries>,
80
+ queryClient?: Tanstack.QueryClient
81
+ ): InferQueryResults<TQueries>;
82
+
83
+ /**
84
+ * @example
85
+ * ```
86
+ * const { data, error, isLoading } = useQueries({
87
+ * queries: [
88
+ * { queryKey: ['lists'], query: 'SELECT * from lists' },
89
+ * { queryKey: ['todos'], query: 'SELECT * from todos' }
90
+ * ],
91
+ * })
92
+ * ```
93
+ *
94
+ * @example
95
+ * ```
96
+ * const ids = [1, 2, 3];
97
+ * const combinedQueries = useQueries({
98
+ * queries: ids.map((id) => ({
99
+ * queryKey: ['post', id],
100
+ * query: 'SELECT * from lists where id = ?',
101
+ * parameters: [id],
102
+ * })),
103
+ * combine: (results) => {
104
+ * return {
105
+ * data: results.map((result) => result.data),
106
+ * pending: results.some((result) => result.isPending),
107
+ * }
108
+ * },
109
+ * });
110
+ * ```
111
+ */
112
+ export function useQueries(
113
+ options: UseQueriesBaseOptions,
114
+ queryClient: Tanstack.QueryClient = Tanstack.useQueryClient()
115
+ ) {
116
+ const powerSync = usePowerSync();
117
+
118
+ if (!powerSync) {
119
+ throw new Error('PowerSync is not available');
120
+ }
121
+
122
+ const queriesInput = options.queries;
123
+
124
+ const powerSyncQueriesInput = useMemo(
125
+ () =>
126
+ queriesInput.map((queryOptions) => ({
127
+ query: queryOptions.query,
128
+ parameters: queryOptions.parameters,
129
+ queryKey: queryOptions.queryKey
130
+ })),
131
+ [queriesInput]
132
+ );
133
+
134
+ const states = usePowerSyncQueries(powerSyncQueriesInput, queryClient);
135
+
136
+ const queries = useMemo(() => {
137
+ return queriesInput.map((queryOptions, idx) => {
138
+ const { query, parameters, ...rest } = queryOptions;
139
+ const state = states[idx];
140
+
141
+ return {
142
+ ...rest,
143
+ queryFn: query ? state.queryFn : rest.queryFn,
144
+ queryKey: rest.queryKey
145
+ };
146
+ });
147
+ }, [queriesInput, states]);
148
+
149
+ return Tanstack.useQueries(
150
+ {
151
+ queries: queries as Tanstack.QueriesOptions<any>,
152
+ combine: options.combine
153
+ ? (results) => {
154
+ const enhancedResultsWithQueryKey = results.map((result, index) => ({
155
+ ...result,
156
+ queryKey: queries[index].queryKey
157
+ }));
158
+
159
+ return options.combine?.(enhancedResultsWithQueryKey);
160
+ }
161
+ : undefined
162
+ },
163
+ queryClient
164
+ );
165
+ }
@@ -1,8 +1,7 @@
1
- import { parseQuery, type CompilableQuery } from '@powersync/common';
1
+ import { type CompilableQuery } from '@powersync/common';
2
2
  import { usePowerSync } from '@powersync/react';
3
- import React from 'react';
4
-
5
3
  import * as Tanstack from '@tanstack/react-query';
4
+ import { usePowerSyncQueries } from './usePowerSyncQueries.js';
6
5
 
7
6
  export type PowerSyncQueryOptions<T> = {
8
7
  query?: string | CompilableQuery<T>;
@@ -11,12 +10,44 @@ export type PowerSyncQueryOptions<T> = {
11
10
 
12
11
  export type UseBaseQueryOptions<TQueryOptions> = TQueryOptions & PowerSyncQueryOptions<any>;
13
12
 
13
+ /**
14
+ *
15
+ * Uses the `queryFn` to execute the query. No different from the base `useQuery` hook.
16
+ *
17
+ * @example
18
+ * ```
19
+ * const { data, error, isLoading } = useQuery({
20
+ * queryKey: ['lists'],
21
+ * queryFn: getTodos,
22
+ * });
23
+ * ```
24
+ */
14
25
  export function useQuery<TData = unknown, TError = Tanstack.DefaultError>(
15
26
  options: UseBaseQueryOptions<Tanstack.UseQueryOptions<TData, TError>> & { query?: undefined },
16
27
  queryClient?: Tanstack.QueryClient
17
28
  ): Tanstack.UseQueryResult<TData, TError>;
18
29
 
19
- // Overload when 'query' is present
30
+ /**
31
+ *
32
+ * Uses the `query` to execute the PowerSync query.
33
+ *
34
+ * @example
35
+ * ```
36
+ * const { data, error, isLoading } = useQuery({
37
+ * queryKey: ['lists'],
38
+ * query: 'SELECT * from lists where id = ?',
39
+ * parameters: ['id-1']
40
+ * });
41
+ * ```
42
+ *
43
+ * @example
44
+ * ```
45
+ * const { data, error, isLoading } = useQuery({
46
+ * queryKey: ['lists'],
47
+ * query: compilableQuery,
48
+ * });
49
+ * ```
50
+ */
20
51
  export function useQuery<TData = unknown, TError = Tanstack.DefaultError>(
21
52
  options: UseBaseQueryOptions<Tanstack.UseQueryOptions<TData[], TError>> & { query: string | CompilableQuery<TData> },
22
53
  queryClient?: Tanstack.QueryClient
@@ -29,12 +60,36 @@ export function useQuery<TData = unknown, TError = Tanstack.DefaultError>(
29
60
  return useQueryCore(options, queryClient, Tanstack.useQuery);
30
61
  }
31
62
 
63
+ /**
64
+ *
65
+ * Uses the `queryFn` to execute the query. No different from the base `useSuspenseQuery` hook.
66
+ *
67
+ * @example
68
+ * ```
69
+ * const { data } = useSuspenseQuery({
70
+ * queryKey: ['lists'],
71
+ * queryFn: getTodos,
72
+ * });
73
+ * ```
74
+ */
32
75
  export function useSuspenseQuery<TData = unknown, TError = Tanstack.DefaultError>(
33
76
  options: UseBaseQueryOptions<Tanstack.UseSuspenseQueryOptions<TData, TError>> & { query?: undefined },
34
77
  queryClient?: Tanstack.QueryClient
35
78
  ): Tanstack.UseSuspenseQueryResult<TData, TError>;
36
79
 
37
- // Overload when 'query' is present
80
+ /***
81
+ *
82
+ * Uses the `query` to execute the PowerSync query.
83
+ *
84
+ * @example
85
+ * ```
86
+ * const { data } = useSuspenseQuery({
87
+ * queryKey: ['lists'],
88
+ * query: 'SELECT * from lists where id = ?',
89
+ * parameters: ['id-1']
90
+ * });
91
+ * ```
92
+ */
38
93
  export function useSuspenseQuery<TData = unknown, TError = Tanstack.DefaultError>(
39
94
  options: UseBaseQueryOptions<Tanstack.UseSuspenseQueryOptions<TData[], TError>> & {
40
95
  query: string | CompilableQuery<TData>;
@@ -65,92 +120,23 @@ function useQueryCore<
65
120
  throw new Error('PowerSync is not available');
66
121
  }
67
122
 
68
- let error: Error | undefined = undefined;
69
-
70
- const [tables, setTables] = React.useState<string[]>([]);
71
- const { query, parameters = [], ...resolvedOptions } = options;
72
-
73
- let sqlStatement = '';
74
- let queryParameters = [];
75
-
76
- if (query) {
77
- try {
78
- const parsedQuery = parseQuery(query, parameters);
79
-
80
- sqlStatement = parsedQuery.sqlStatement;
81
- queryParameters = parsedQuery.parameters;
82
- } catch (e) {
83
- error = e;
84
- }
85
- }
86
-
87
- const stringifiedParams = JSON.stringify(queryParameters);
88
- const stringifiedKey = JSON.stringify(options.queryKey);
89
-
90
- const fetchTables = async () => {
91
- try {
92
- const tables = await powerSync.resolveTables(sqlStatement, queryParameters);
93
- setTables(tables);
94
- } catch (e) {
95
- error = e;
96
- }
97
- };
98
-
99
- React.useEffect(() => {
100
- if (error || !query) return () => {};
101
-
102
- (async () => {
103
- await fetchTables();
104
- })();
123
+ const { query, parameters, queryKey, ...resolvedOptions } = options;
105
124
 
106
- const l = powerSync.registerListener({
107
- schemaChanged: async () => {
108
- await fetchTables();
109
- queryClient.invalidateQueries({ queryKey: options.queryKey });
110
- }
111
- });
112
-
113
- return () => l?.();
114
- }, [powerSync, sqlStatement, stringifiedParams]);
115
-
116
- const queryFn = React.useCallback(async () => {
117
- if (error) {
118
- return Promise.reject(error);
119
- }
120
-
121
- try {
122
- return typeof query == 'string' ? powerSync.getAll<TData>(sqlStatement, queryParameters) : query.execute();
123
- } catch (e) {
124
- return Promise.reject(e);
125
- }
126
- }, [powerSync, query, parameters, stringifiedKey]);
127
-
128
- React.useEffect(() => {
129
- if (error || !query) return () => {};
130
-
131
- const abort = new AbortController();
132
- powerSync.onChangeWithCallback(
133
- {
134
- onChange: () => {
135
- queryClient.invalidateQueries({
136
- queryKey: options.queryKey
137
- });
138
- },
139
- onError: (e) => {
140
- error = e;
141
- }
142
- },
125
+ const [{ queryFn }] = usePowerSyncQueries(
126
+ [
143
127
  {
144
- tables,
145
- signal: abort.signal
128
+ query,
129
+ parameters,
130
+ queryKey
146
131
  }
147
- );
148
- return () => abort.abort();
149
- }, [powerSync, queryClient, stringifiedKey, tables]);
132
+ ],
133
+ queryClient
134
+ );
150
135
 
151
136
  return useQueryFn(
152
137
  {
153
138
  ...(resolvedOptions as TQueryOptions),
139
+ queryKey,
154
140
  queryFn: query ? queryFn : resolvedOptions.queryFn
155
141
  } as TQueryOptions,
156
142
  queryClient
package/src/index.ts CHANGED
@@ -1 +1,3 @@
1
1
  export { useQuery, useSuspenseQuery } from './hooks/useQuery.js';
2
+ export { useQueries } from './hooks/useQueries.js';
3
+