Skip to content

Commit c94be6a

Browse files
DominicGBauerDominicGBauer
andauthored
feat(reat): allow compilable query as hook argument (#150)
Co-authored-by: DominicGBauer <dominic@nomanini.com>
1 parent c5a9eb5 commit c94be6a

File tree

13 files changed

+334
-17
lines changed

13 files changed

+334
-17
lines changed

.changeset/khaki-apples-hunt.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
"@powersync/common": minor
3+
"@powersync/react": minor
4+
---
5+
6+
Allow compilable queries to be used as hook arguments

demos/django-react-native-todolist/library/widgets/HeaderWidget.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,7 @@ export const HeaderWidget: React.FC<{
3939
onPress={() => {
4040
Alert.alert(
4141
'Status',
42-
`${status.connected ? 'Connected' : 'Disconnected'}. \nLast Synced at ${
43-
status.lastSyncedAt?.toISOString() ?? '-'
42+
`${status.connected ? 'Connected' : 'Disconnected'}. \nLast Synced at ${status.lastSyncedAt?.toISOString() ?? '-'
4443
}\nVersion: ${powersync.sdkVersion}`
4544
);
4645
}}

demos/react-native-supabase-group-chat/src/app/(app)/contacts/index.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ export default function ContactsIndex() {
112112
icon={<Search size="$1.5" />}
113113
backgroundColor="$brand1"
114114
borderRadius="$3"
115-
// circular
115+
// circular
116116
/>
117117
</XStack>
118118

packages/common/package.json

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"homepage": "https://docs.powersync.com/resources/api-reference",
2424
"scripts": {
2525
"build": "tsc -b",
26-
"clean": "rm -rf lib tsconfig.tsbuildinfo"
26+
"clean": "rm -rf lib tsconfig.tsbuildinfo",
27+
"test": "vitest"
2728
},
2829
"dependencies": {
2930
"async-mutex": "^0.4.0",
@@ -37,6 +38,7 @@
3738
"@types/lodash": "^4.14.197",
3839
"@types/node": "^20.5.9",
3940
"@types/uuid": "^9.0.1",
40-
"typescript": "^5.1.3"
41+
"typescript": "^5.1.3",
42+
"vitest": "^1.5.2"
4143
}
4244
}

packages/common/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,6 @@ export * from './db/schema/TableV2';
3030
export * from './utils/AbortOperation';
3131
export * from './utils/BaseObserver';
3232
export * from './utils/strings';
33+
export * from './utils/parseQuery';
34+
35+
export * from './types/types';

packages/common/src/types/types.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
export interface CompilableQuery<T> {
2+
execute(): Promise<T[]>;
3+
compile(): CompiledQuery;
4+
}
5+
6+
export interface CompiledQuery {
7+
readonly sql: string;
8+
readonly parameters: ReadonlyArray<unknown>;
9+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
import type { CompilableQuery } from '../types/types';
2+
3+
export interface ParsedQuery {
4+
sqlStatement: string;
5+
parameters: any[];
6+
}
7+
8+
export const parseQuery = <T>(query: string | CompilableQuery<T>, parameters: any[]): ParsedQuery => {
9+
let sqlStatement: string;
10+
11+
if (typeof query == 'string') {
12+
sqlStatement = query;
13+
} else {
14+
const hasAdditionalParameters = parameters.length > 0;
15+
if (hasAdditionalParameters) {
16+
throw new Error('You cannot pass parameters to a compiled query.');
17+
}
18+
19+
const compiled = query.compile();
20+
sqlStatement = compiled.sql;
21+
parameters = compiled.parameters as any[];
22+
}
23+
24+
return { sqlStatement, parameters: parameters };
25+
};
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": "./",
4+
"esModuleInterop": true,
5+
"jsx": "react",
6+
"rootDir": "../",
7+
"composite": true,
8+
"outDir": "./lib",
9+
"lib": ["esnext", "DOM"],
10+
"module": "esnext",
11+
"sourceMap": true,
12+
"moduleResolution": "node",
13+
"noFallthroughCasesInSwitch": true,
14+
"noImplicitReturns": true,
15+
"noImplicitUseStrict": false,
16+
"noStrictGenericChecks": false,
17+
"resolveJsonModule": true,
18+
"skipLibCheck": true,
19+
"target": "esnext"
20+
},
21+
"include": ["../src/**/*"]
22+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
import { describe, expect, it } from 'vitest';
2+
import * as SUT from '../../src/utils/parseQuery';
3+
4+
describe('parseQuery', () => {
5+
it('should do nothing if the query is a string', () => {
6+
const query = 'SELECT * FROM table';
7+
const parameters = ['one'];
8+
const result = SUT.parseQuery(query, parameters);
9+
10+
expect(result).toEqual({ sqlStatement: query, parameters: ['one'] });
11+
});
12+
13+
it('should compile the query and return the sql statement and parameters if the query is compilable', () => {
14+
const sqlStatement = 'SELECT * FROM table';
15+
const parameters = [];
16+
const query = {
17+
compile: () => ({ sql: sqlStatement, parameters: ['test'] }),
18+
execute: () => Promise.resolve([])
19+
};
20+
const result = SUT.parseQuery(query, parameters);
21+
22+
expect(result).toEqual({ sqlStatement, parameters: ['test'] });
23+
});
24+
25+
it('should throw an error if there is an additional parameter included in a compiled query', () => {
26+
const sqlStatement = 'SELECT * FROM table';
27+
const parameters = ['additional parameter'];
28+
const query = {
29+
compile: () => ({ sql: sqlStatement, parameters: ['test'] }),
30+
execute: () => Promise.resolve([])
31+
};
32+
const result = () => SUT.parseQuery(query, parameters);
33+
34+
expect(result).toThrowError('You cannot pass parameters to a compiled query.');
35+
});
36+
});

packages/react/README.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,14 @@ const Component = () => {
6363

6464
### Queries
6565

66-
Queries will automatically update when a dependant table is updated unless you set the `runQueryOnce` flag.
66+
Queries will automatically update when a dependant table is updated unless you set the `runQueryOnce` flag. You are also able to use a compilable query (e.g. [Kysely queries](https://github.com/powersync-ja/powersync-js/tree/main/packages/kysely-driver)) as a query argument in place of a SQL statement string.
6767

6868
```JSX
6969
// TodoListDisplay.jsx
7070
import { useQuery } from "@powersync/react";
7171

7272
export const TodoListDisplay = () => {
73-
const { data: todoLists } = useQuery('SELECT * from lists');
73+
const { data: todoLists } = useQuery('SELECT * FROM lists WHERE id = ?', ['id-1'], {runQueryOnce: false});
7474

7575
return <View>
7676
{todoLists.map((l) => (

0 commit comments

Comments
 (0)