Skip to content

Commit c04ecfc

Browse files
[Fix] Custom Query Executor Hooks (#295)
1 parent f202944 commit c04ecfc

File tree

7 files changed

+69
-14
lines changed

7 files changed

+69
-14
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'@powersync/kysely-driver': minor
3+
---
4+
5+
Made `dialect` in `wrapPowerSyncWithKysely` options optional since the method provides a PowerSync dialect by default.

.changeset/orange-points-speak.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@powersync/react': patch
3+
'@powersync/vue': patch
4+
---
5+
6+
React and Vue helpers should execute queries from compatible query executor methods. This should allow Kysely queries with plugins to function correctly.

packages/kysely-driver/src/sqlite/db.ts

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1-
import { PowerSyncDialect } from './sqlite-dialect';
2-
import { Kysely, type KyselyConfig } from 'kysely';
31
import { type AbstractPowerSyncDatabase } from '@powersync/common';
2+
import { Dialect, Kysely, type KyselyConfig } from 'kysely';
3+
import { PowerSyncDialect } from './sqlite-dialect';
4+
5+
/**
6+
* An extension of {@link KyselyConfig} which uses the {@link PowerSyncDialect} by default.
7+
*/
8+
export type PowerSyncKyselyOptions = Omit<KyselyConfig, 'dialect'> & {
9+
dialect?: Dialect;
10+
};
411

5-
export const wrapPowerSyncWithKysely = <T>(db: AbstractPowerSyncDatabase, options?: KyselyConfig) => {
12+
export const wrapPowerSyncWithKysely = <T>(db: AbstractPowerSyncDatabase, options?: PowerSyncKyselyOptions) => {
613
return new Kysely<T>({
714
dialect: new PowerSyncDialect({
815
db

packages/react/src/hooks/useQuery.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type SQLWatchOptions, parseQuery, type CompilableQuery, type ParsedQuery } from '@powersync/common';
1+
import { parseQuery, type CompilableQuery, type ParsedQuery, type SQLWatchOptions } from '@powersync/common';
22
import React from 'react';
33
import { usePowerSync } from './PowerSyncContext';
44

@@ -85,7 +85,8 @@ export const useQuery = <T = any>(
8585
const fetchData = async () => {
8686
setIsFetching(true);
8787
try {
88-
const result = await powerSync.getAll<T>(sqlStatement, queryParameters);
88+
const result =
89+
typeof query == 'string' ? await powerSync.getAll<T>(sqlStatement, queryParameters) : await query.execute();
8990
handleResult(result);
9091
} catch (e) {
9192
console.error('Failed to fetch data:', e);

packages/react/tests/useQuery.test.tsx

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
1-
import React from 'react';
1+
import * as commonSdk from '@powersync/common';
22
import { renderHook, waitFor } from '@testing-library/react';
3-
import { vi, describe, expect, it, afterEach } from 'vitest';
4-
import { useQuery } from '../src/hooks/useQuery';
3+
import React from 'react';
4+
import { afterEach, describe, expect, it, vi } from 'vitest';
55
import { PowerSyncContext } from '../src/hooks/PowerSyncContext';
6-
import * as commonSdk from '@powersync/common';
6+
import { useQuery } from '../src/hooks/useQuery';
77

88
const mockPowerSync = {
99
currentStatus: { status: 'initial' },
@@ -156,6 +156,23 @@ describe('useQuery', () => {
156156
expect(currentResult.isLoading).toEqual(true);
157157
});
158158

159+
it('should execute compatible queries', async () => {
160+
const wrapper = ({ children }) => (
161+
<PowerSyncContext.Provider value={mockPowerSync as any}>{children}</PowerSyncContext.Provider>
162+
);
163+
164+
const query = () =>
165+
useQuery({
166+
execute: () => [{ test: 'custom' }] as any,
167+
compile: () => ({ sql: 'SELECT * from lists', parameters: [] })
168+
});
169+
const { result } = renderHook(query, { wrapper });
170+
171+
await vi.waitFor(() => {
172+
expect(result.current.data[0]?.test).toEqual('custom');
173+
});
174+
});
175+
159176
// The test returns unhandled errors when run with all the others.
160177
// TODO: Fix the test so that there are no unhandled errors (this may be a vitest or @testing-library/react issue)
161178
it.skip('should show an error if parsing the query results in an error', async () => {

packages/vue/src/composables/useQuery.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { type SQLWatchOptions, parseQuery, type CompilableQuery, ParsedQuery } from '@powersync/common';
1+
import { type CompilableQuery, ParsedQuery, type SQLWatchOptions, parseQuery } from '@powersync/common';
22
import { type MaybeRef, type Ref, ref, toValue, watchEffect } from 'vue';
33
import { usePowerSync } from './powerSync';
44

@@ -87,10 +87,10 @@ export const useQuery = <T = any>(
8787
error.value = wrappedError;
8888
};
8989

90-
const _fetchData = async (sql: string, parameters: any[]) => {
90+
const _fetchData = async (executor: () => Promise<T[]>) => {
9191
isFetching.value = true;
9292
try {
93-
const result = await powerSync.value.getAll<T>(sql, parameters);
93+
const result = await executor();
9494
handleResult(result);
9595
} catch (e) {
9696
console.error('Failed to fetch data:', e);
@@ -104,8 +104,9 @@ export const useQuery = <T = any>(
104104
onCleanup(() => abortController.abort());
105105

106106
let parsedQuery: ParsedQuery;
107+
const queryValue = toValue(query);
107108
try {
108-
parsedQuery = parseQuery(toValue(query), toValue(sqlParameters).map(toValue));
109+
parsedQuery = parseQuery(queryValue, toValue(sqlParameters).map(toValue));
109110
} catch (e) {
110111
console.error('Failed to parse query:', e);
111112
handleError(e);
@@ -123,7 +124,9 @@ export const useQuery = <T = any>(
123124
return;
124125
}
125126
// Fetch initial data
126-
fetchData = () => _fetchData(sql, parameters);
127+
const executor =
128+
typeof queryValue == 'string' ? () => powerSync.value.getAll<T>(sql, parameters) : () => queryValue.execute();
129+
fetchData = () => _fetchData(executor);
127130
await fetchData();
128131

129132
if (options.runQueryOnce) {

packages/vue/tests/useQuery.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,22 @@ describe('useQuery', () => {
144144
expect(isLoading.value).toEqual(false);
145145
});
146146

147+
it('should execute compilable queries', async () => {
148+
vi.spyOn(PowerSync, 'usePowerSync').mockReturnValue(ref(mockPowerSync) as any);
149+
150+
const [{ isLoading, data }] = withSetup(() =>
151+
useQuery({
152+
execute: () => [{ test: 'custom' }] as any,
153+
compile: () => ({ sql: 'SELECT * from lists', parameters: [] })
154+
})
155+
);
156+
157+
expect(isLoading.value).toEqual(true);
158+
await flushPromises();
159+
expect(isLoading.value).toEqual(false);
160+
expect(data.value[0].test).toEqual('custom');
161+
});
162+
147163
it('should set error for compilable query on useQuery parameters', async () => {
148164
vi.spyOn(PowerSync, 'usePowerSync').mockReturnValue(ref(mockPowerSync) as any);
149165

0 commit comments

Comments
 (0)