@powersync/op-sqlite 0.0.0-dev-20241014170110 → 0.0.0-dev-20241107150304
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 +26 -1
- package/lib/commonjs/db/OPSqliteAdapter.js +66 -46
- package/lib/commonjs/db/OPSqliteAdapter.js.map +1 -1
- package/lib/commonjs/db/SqliteOptions.js +2 -1
- package/lib/commonjs/db/SqliteOptions.js.map +1 -1
- package/lib/module/db/OPSqliteAdapter.js +66 -46
- package/lib/module/db/OPSqliteAdapter.js.map +1 -1
- package/lib/module/db/SqliteOptions.js +2 -1
- package/lib/module/db/SqliteOptions.js.map +1 -1
- package/lib/typescript/commonjs/src/db/OPSqliteAdapter.d.ts +8 -2
- package/lib/typescript/commonjs/src/db/OPSqliteAdapter.d.ts.map +1 -1
- package/lib/typescript/commonjs/src/db/SqliteOptions.d.ts +5 -0
- package/lib/typescript/commonjs/src/db/SqliteOptions.d.ts.map +1 -1
- package/lib/typescript/commonjs/tsconfig.build.tsbuildinfo +1 -1
- package/lib/typescript/module/src/db/OPSqliteAdapter.d.ts +8 -2
- package/lib/typescript/module/src/db/OPSqliteAdapter.d.ts.map +1 -1
- package/lib/typescript/module/src/db/SqliteOptions.d.ts +5 -0
- package/lib/typescript/module/src/db/SqliteOptions.d.ts.map +1 -1
- package/lib/typescript/module/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +6 -6
- package/powersync-op-sqlite.podspec +1 -1
- package/src/db/OPSqliteAdapter.ts +67 -52
- package/src/db/SqliteOptions.ts +9 -2
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powersync/op-sqlite",
|
|
3
|
-
"version": "0.0.0-dev-
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.0.0-dev-20241107150304",
|
|
4
|
+
"description": "PowerSync - sync Postgres or MongoDB with SQLite in your React Native app for offline-first and real-time data",
|
|
5
5
|
"source": "./src/index.ts",
|
|
6
6
|
"main": "./lib/commonjs/index.js",
|
|
7
7
|
"module": "./lib/module/index.js",
|
|
@@ -58,17 +58,17 @@
|
|
|
58
58
|
"access": "public"
|
|
59
59
|
},
|
|
60
60
|
"peerDependencies": {
|
|
61
|
-
"@op-engineering/op-sqlite": "^9.1
|
|
62
|
-
"@powersync/common": "
|
|
61
|
+
"@op-engineering/op-sqlite": "^9.2.1",
|
|
62
|
+
"@powersync/common": "0.0.0-dev-20241107150304",
|
|
63
63
|
"react": "*",
|
|
64
64
|
"react-native": "*"
|
|
65
65
|
},
|
|
66
66
|
"dependencies": {
|
|
67
67
|
"async-lock": "^1.4.0",
|
|
68
|
-
"@powersync/common": "
|
|
68
|
+
"@powersync/common": "0.0.0-dev-20241107150304"
|
|
69
69
|
},
|
|
70
70
|
"devDependencies": {
|
|
71
|
-
"@op-engineering/op-sqlite": "^9.1
|
|
71
|
+
"@op-engineering/op-sqlite": "^9.2.1",
|
|
72
72
|
"@react-native/eslint-config": "^0.73.1",
|
|
73
73
|
"@types/async-lock": "^1.4.0",
|
|
74
74
|
"@types/react": "^18.2.44",
|
|
@@ -11,7 +11,7 @@ Pod::Spec.new do |s|
|
|
|
11
11
|
s.authors = package["author"]
|
|
12
12
|
|
|
13
13
|
s.platforms = { :ios => min_ios_version_supported }
|
|
14
|
-
s.source = { :git => "https://github.com/
|
|
14
|
+
s.source = { :git => "https://github.com/powersync-ja/powersync-js.git", :tag => "#{s.version}" }
|
|
15
15
|
|
|
16
16
|
s.source_files = "ios/**/*.{h,m,mm,cpp}"
|
|
17
17
|
|
|
@@ -11,7 +11,7 @@ import { ANDROID_DATABASE_PATH, IOS_LIBRARY_PATH, open, type DB } from '@op-engi
|
|
|
11
11
|
import Lock from 'async-lock';
|
|
12
12
|
import { OPSQLiteConnection } from './OPSQLiteConnection';
|
|
13
13
|
import { NativeModules, Platform } from 'react-native';
|
|
14
|
-
import {
|
|
14
|
+
import { SqliteOptions } from './SqliteOptions';
|
|
15
15
|
|
|
16
16
|
/**
|
|
17
17
|
* Adapter for React Native Quick SQLite
|
|
@@ -35,10 +35,12 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
|
|
|
35
35
|
|
|
36
36
|
protected initialized: Promise<void>;
|
|
37
37
|
|
|
38
|
-
protected readConnections: OPSQLiteConnection
|
|
38
|
+
protected readConnections: Array<{ busy: boolean; connection: OPSQLiteConnection }> | null;
|
|
39
39
|
|
|
40
40
|
protected writeConnection: OPSQLiteConnection | null;
|
|
41
41
|
|
|
42
|
+
private readQueue: Array<() => void> = [];
|
|
43
|
+
|
|
42
44
|
constructor(protected options: OPSQLiteAdapterOptions) {
|
|
43
45
|
super();
|
|
44
46
|
this.name = this.options.name;
|
|
@@ -50,15 +52,10 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
|
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
protected async init() {
|
|
53
|
-
const { lockTimeoutMs, journalMode, journalSizeLimit, synchronous } = this.options.sqliteOptions;
|
|
54
|
-
// const { dbFilename, dbLocation } = this.options;
|
|
55
|
+
const { lockTimeoutMs, journalMode, journalSizeLimit, synchronous, encryptionKey } = this.options.sqliteOptions;
|
|
55
56
|
const dbFilename = this.options.name;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const DB: DB = open({
|
|
59
|
-
name: dbFilename,
|
|
60
|
-
location: location
|
|
61
|
-
});
|
|
57
|
+
|
|
58
|
+
this.writeConnection = await this.openConnection(dbFilename);
|
|
62
59
|
|
|
63
60
|
const statements: string[] = [
|
|
64
61
|
`PRAGMA busy_timeout = ${lockTimeoutMs}`,
|
|
@@ -70,7 +67,7 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
|
|
|
70
67
|
for (const statement of statements) {
|
|
71
68
|
for (let tries = 0; tries < 30; tries++) {
|
|
72
69
|
try {
|
|
73
|
-
await
|
|
70
|
+
await this.writeConnection!.execute(statement);
|
|
74
71
|
break;
|
|
75
72
|
} catch (e: any) {
|
|
76
73
|
if (e instanceof Error && e.message.includes('database is locked') && tries < 29) {
|
|
@@ -82,34 +79,24 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
|
|
|
82
79
|
}
|
|
83
80
|
}
|
|
84
81
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
// Changes should only occur in the write connection
|
|
83
|
+
this.writeConnection!.registerListener({
|
|
84
|
+
tablesUpdated: (notification) => this.iterateListeners((cb) => cb.tablesUpdated?.(notification))
|
|
85
|
+
});
|
|
88
86
|
|
|
89
87
|
this.readConnections = [];
|
|
90
88
|
for (let i = 0; i < READ_CONNECTIONS; i++) {
|
|
91
89
|
// Workaround to create read-only connections
|
|
92
90
|
let dbName = './'.repeat(i + 1) + dbFilename;
|
|
93
|
-
const conn = await this.openConnection(
|
|
91
|
+
const conn = await this.openConnection(dbName);
|
|
94
92
|
await conn.execute('PRAGMA query_only = true');
|
|
95
|
-
this.readConnections.push(conn);
|
|
93
|
+
this.readConnections.push({ busy: false, connection: conn });
|
|
96
94
|
}
|
|
97
|
-
|
|
98
|
-
this.writeConnection = new OPSQLiteConnection({
|
|
99
|
-
baseDB: DB
|
|
100
|
-
});
|
|
101
|
-
|
|
102
|
-
// Changes should only occur in the write connection
|
|
103
|
-
this.writeConnection!.registerListener({
|
|
104
|
-
tablesUpdated: (notification) => this.iterateListeners((cb) => cb.tablesUpdated?.(notification))
|
|
105
|
-
});
|
|
106
95
|
}
|
|
107
96
|
|
|
108
|
-
protected async openConnection(
|
|
109
|
-
const
|
|
110
|
-
|
|
111
|
-
location: dbLocation
|
|
112
|
-
});
|
|
97
|
+
protected async openConnection(filenameOverride?: string): Promise<OPSQLiteConnection> {
|
|
98
|
+
const dbFilename = filenameOverride ?? this.options.name;
|
|
99
|
+
const DB: DB = this.openDatabase(dbFilename, this.options.sqliteOptions.encryptionKey);
|
|
113
100
|
|
|
114
101
|
//Load extension for all connections
|
|
115
102
|
this.loadExtension(DB);
|
|
@@ -129,6 +116,24 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
|
|
|
129
116
|
}
|
|
130
117
|
}
|
|
131
118
|
|
|
119
|
+
private openDatabase(dbFilename: string, encryptionKey?: string): DB {
|
|
120
|
+
//This is needed because an undefined/null dbLocation will cause the open function to fail
|
|
121
|
+
const location = this.getDbLocation(this.options.dbLocation);
|
|
122
|
+
//Simarlily if the encryption key is undefined/null when using SQLCipher it will cause the open function to fail
|
|
123
|
+
if (encryptionKey) {
|
|
124
|
+
return open({
|
|
125
|
+
name: dbFilename,
|
|
126
|
+
location: location,
|
|
127
|
+
encryptionKey: encryptionKey
|
|
128
|
+
});
|
|
129
|
+
} else {
|
|
130
|
+
return open({
|
|
131
|
+
name: dbFilename,
|
|
132
|
+
location: location
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
132
137
|
private loadExtension(DB: DB) {
|
|
133
138
|
if (Platform.OS === 'ios') {
|
|
134
139
|
const bundlePath: string = NativeModules.PowerSyncOpSqlite.getBundlePath();
|
|
@@ -142,36 +147,46 @@ export class OPSQLiteDBAdapter extends BaseObserver<DBAdapterListener> implement
|
|
|
142
147
|
close() {
|
|
143
148
|
this.initialized.then(() => {
|
|
144
149
|
this.writeConnection!.close();
|
|
145
|
-
this.readConnections!.forEach((c) => c.close());
|
|
150
|
+
this.readConnections!.forEach((c) => c.connection.close());
|
|
146
151
|
});
|
|
147
152
|
}
|
|
148
153
|
|
|
149
154
|
async readLock<T>(fn: (tx: OPSQLiteConnection) => Promise<T>, options?: DBLockOptions): Promise<T> {
|
|
150
155
|
await this.initialized;
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
156
|
+
return new Promise(async (resolve, reject) => {
|
|
157
|
+
const execute = async () => {
|
|
158
|
+
// Find an available connection that is not busy
|
|
159
|
+
const availableConnection = this.readConnections!.find((conn) => !conn.busy);
|
|
160
|
+
|
|
161
|
+
// If we have an available connection, use it
|
|
162
|
+
if (availableConnection) {
|
|
163
|
+
availableConnection.busy = true;
|
|
164
|
+
try {
|
|
165
|
+
resolve(await fn(availableConnection.connection));
|
|
166
|
+
} catch (error) {
|
|
167
|
+
reject(error);
|
|
168
|
+
} finally {
|
|
169
|
+
availableConnection.busy = false;
|
|
170
|
+
// After query execution, process any queued tasks
|
|
171
|
+
this.processQueue();
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
// If no available connections, add to the queue
|
|
175
|
+
this.readQueue.push(execute);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
execute();
|
|
160
180
|
});
|
|
181
|
+
}
|
|
161
182
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
resolve(await fn(sortedConnections[0].connection));
|
|
168
|
-
},
|
|
169
|
-
{ timeout: options?.timeoutMs }
|
|
170
|
-
);
|
|
171
|
-
} catch (ex) {
|
|
172
|
-
reject(ex);
|
|
183
|
+
private async processQueue(): Promise<void> {
|
|
184
|
+
if (this.readQueue.length > 0) {
|
|
185
|
+
const next = this.readQueue.shift();
|
|
186
|
+
if (next) {
|
|
187
|
+
next();
|
|
173
188
|
}
|
|
174
|
-
}
|
|
189
|
+
}
|
|
175
190
|
}
|
|
176
191
|
|
|
177
192
|
async writeLock<T>(fn: (tx: OPSQLiteConnection) => Promise<T>, options?: DBLockOptions): Promise<T> {
|
package/src/db/SqliteOptions.ts
CHANGED
|
@@ -23,6 +23,12 @@ export interface SqliteOptions {
|
|
|
23
23
|
* Set to null or zero to fail immediately when the database is locked.
|
|
24
24
|
*/
|
|
25
25
|
lockTimeoutMs?: number;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Encryption key for the database.
|
|
29
|
+
* If set, the database will be encrypted using SQLCipher.
|
|
30
|
+
*/
|
|
31
|
+
encryptionKey?: string;
|
|
26
32
|
}
|
|
27
33
|
|
|
28
34
|
// SQLite journal mode. Set on the primary connection.
|
|
@@ -36,14 +42,14 @@ enum SqliteJournalMode {
|
|
|
36
42
|
truncate = 'TRUNCATE',
|
|
37
43
|
persist = 'PERSIST',
|
|
38
44
|
memory = 'MEMORY',
|
|
39
|
-
off = 'OFF'
|
|
45
|
+
off = 'OFF'
|
|
40
46
|
}
|
|
41
47
|
|
|
42
48
|
// SQLite file commit mode.
|
|
43
49
|
enum SqliteSynchronous {
|
|
44
50
|
normal = 'NORMAL',
|
|
45
51
|
full = 'FULL',
|
|
46
|
-
off = 'OFF'
|
|
52
|
+
off = 'OFF'
|
|
47
53
|
}
|
|
48
54
|
|
|
49
55
|
export const DEFAULT_SQLITE_OPTIONS: Required<SqliteOptions> = {
|
|
@@ -51,4 +57,5 @@ export const DEFAULT_SQLITE_OPTIONS: Required<SqliteOptions> = {
|
|
|
51
57
|
synchronous: SqliteSynchronous.normal,
|
|
52
58
|
journalSizeLimit: 6 * 1024 * 1024,
|
|
53
59
|
lockTimeoutMs: 30000,
|
|
60
|
+
encryptionKey: null
|
|
54
61
|
};
|