-
Notifications
You must be signed in to change notification settings - Fork 44
/
Copy pathRNQSDBAdapter.ts
131 lines (114 loc) · 4.22 KB
/
RNQSDBAdapter.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
import {
BaseObserver,
DBAdapter,
DBAdapterListener,
LockContext as PowerSyncLockContext,
Transaction as PowerSyncTransaction,
DBLockOptions,
DBGetUtils,
QueryResult
} from '@powersync/common';
import type { QuickSQLiteConnection } from '@journeyapps/react-native-quick-sqlite';
/**
* Adapter for React Native Quick SQLite
*/
export class RNQSDBAdapter extends BaseObserver<DBAdapterListener> implements DBAdapter {
getAll: <T>(sql: string, parameters?: any[]) => Promise<T[]>;
getOptional: <T>(sql: string, parameters?: any[]) => Promise<T | null>;
get: <T>(sql: string, parameters?: any[]) => Promise<T>;
constructor(
protected baseDB: QuickSQLiteConnection,
public name: string
) {
super();
// link table update commands
baseDB.registerTablesChangedHook((update) => {
this.iterateListeners((cb) => cb.tablesUpdated?.(update));
});
const topLevelUtils = this.generateDBHelpers({
// Arrow function binds `this` for use in readOnlyExecute
execute: (sql: string, params?: any[]) => this.readOnlyExecute(sql, params)
});
// Only assigning get helpers
this.getAll = topLevelUtils.getAll;
this.getOptional = topLevelUtils.getOptional;
this.get = topLevelUtils.get;
}
close() {
return this.baseDB.close();
}
readLock<T>(fn: (tx: PowerSyncLockContext) => Promise<T>, options?: DBLockOptions): Promise<T> {
return this.baseDB.readLock((dbTx) => fn(this.generateDBHelpers(dbTx)), options);
}
readTransaction<T>(fn: (tx: PowerSyncTransaction) => Promise<T>, options?: DBLockOptions): Promise<T> {
return this.baseDB.readTransaction((dbTx) => fn(this.generateDBHelpers(dbTx)), options);
}
writeLock<T>(fn: (tx: PowerSyncLockContext) => Promise<T>, options?: DBLockOptions): Promise<T> {
return this.baseDB.writeLock((dbTx) => fn(this.generateDBHelpers(dbTx)), options);
}
writeTransaction<T>(fn: (tx: PowerSyncTransaction) => Promise<T>, options?: DBLockOptions): Promise<T> {
return this.baseDB.writeTransaction((dbTx) => fn(this.generateDBHelpers(dbTx)), options);
}
execute(query: string, params?: any[]) {
return this.baseDB.execute(query, params);
}
async executeBatch(query: string, params: any[][] = []): Promise<QueryResult> {
const commands: any[] = [];
for (let i = 0; i < params.length; i++) {
commands.push([query, params[i]]);
}
const result = await this.baseDB.executeBatch(commands);
return {
rowsAffected: result.rowsAffected ? result.rowsAffected : 0
};
}
/**
* This provides a top-level read only execute method which is executed inside a read-lock.
* This is necessary since the high level `execute` method uses a write-lock under
* the hood. Helper methods such as `get`, `getAll` and `getOptional` are read only,
* and should use this method.
*/
private readOnlyExecute(sql: string, params?: any[]) {
return this.baseDB.readLock((ctx) => ctx.execute(sql, params));
}
/**
* Adds DB get utils to lock contexts and transaction contexts
* @param tx
* @returns
*/
private generateDBHelpers<T extends { execute: (sql: string, params?: any[]) => Promise<QueryResult> }>(
tx: T
): T & DBGetUtils {
return {
...tx,
/**
* Execute a read-only query and return results
*/
getAll: async <T>(sql: string, parameters?: any[]): Promise<T[]> => {
const res = await tx.execute(sql, parameters);
return res.rows?._array ?? [];
},
/**
* Execute a read-only query and return the first result, or null if the ResultSet is empty.
*/
getOptional: async <T>(sql: string, parameters?: any[]): Promise<T | null> => {
const res = await tx.execute(sql, parameters);
return res.rows?.item(0) ?? null;
},
/**
* Execute a read-only query and return the first result, error if the ResultSet is empty.
*/
get: async <T>(sql: string, parameters?: any[]): Promise<T> => {
const res = await tx.execute(sql, parameters);
const first = res.rows?.item(0);
if (!first) {
throw new Error('Result set is empty');
}
return first;
}
};
}
async refreshSchema() {
await this.baseDB.refreshSchema();
}
}