@powersync/web 1.32.0 → 1.33.0
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/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js +21 -10
- package/dist/_journeyapps_wa-sqlite-_journeyapps_wa-sqlite_src_examples_AccessHandlePoolVFS_js-_journeyapp-89f0ba.index.umd.js.map +1 -1
- package/dist/index.umd.js +177 -45
- package/dist/index.umd.js.map +1 -1
- package/dist/worker/SharedSyncImplementation.umd.js +1268 -349
- package/dist/worker/SharedSyncImplementation.umd.js.map +1 -1
- package/dist/worker/WASQLiteDB.umd.js +1268 -360
- package/dist/worker/WASQLiteDB.umd.js.map +1 -1
- package/lib/package.json +2 -2
- package/lib/src/attachments/IndexDBFileSystemAdapter.d.ts +25 -0
- package/lib/src/attachments/IndexDBFileSystemAdapter.js +104 -0
- package/lib/src/db/adapters/LockedAsyncDatabaseAdapter.js +21 -10
- package/lib/src/index.d.ts +1 -0
- package/lib/src/index.js +1 -0
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/attachments/IndexDBFileSystemAdapter.ts +117 -0
- package/src/db/adapters/LockedAsyncDatabaseAdapter.ts +19 -11
- package/src/index.ts +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@powersync/web",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.33.0",
|
|
4
4
|
"description": "PowerSync Web SDK",
|
|
5
5
|
"main": "lib/src/index.js",
|
|
6
6
|
"module": "lib/src/index.js",
|
|
@@ -56,14 +56,14 @@
|
|
|
56
56
|
"license": "Apache-2.0",
|
|
57
57
|
"peerDependencies": {
|
|
58
58
|
"@journeyapps/wa-sqlite": "^1.4.1",
|
|
59
|
-
"@powersync/common": "^1.
|
|
59
|
+
"@powersync/common": "^1.47.0"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"async-mutex": "^0.5.0",
|
|
63
63
|
"bson": "^6.10.4",
|
|
64
64
|
"comlink": "^4.4.2",
|
|
65
65
|
"commander": "^12.1.0",
|
|
66
|
-
"@powersync/common": "1.
|
|
66
|
+
"@powersync/common": "1.47.0"
|
|
67
67
|
},
|
|
68
68
|
"devDependencies": {
|
|
69
69
|
"@journeyapps/wa-sqlite": "^1.4.1",
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { AttachmentData, EncodingType, LocalStorageAdapter } from '@powersync/common';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* IndexDBFileSystemStorageAdapter implements LocalStorageAdapter using IndexedDB.
|
|
5
|
+
* Suitable for web browsers and web-based environments.
|
|
6
|
+
*/
|
|
7
|
+
export class IndexDBFileSystemStorageAdapter implements LocalStorageAdapter {
|
|
8
|
+
private dbPromise!: Promise<IDBDatabase>;
|
|
9
|
+
|
|
10
|
+
constructor(private databaseName: string = 'PowerSyncFiles') {}
|
|
11
|
+
|
|
12
|
+
async initialize(): Promise<void> {
|
|
13
|
+
this.dbPromise = new Promise<IDBDatabase>((resolve, reject) => {
|
|
14
|
+
const request = indexedDB.open(this.databaseName, 1);
|
|
15
|
+
request.onupgradeneeded = () => {
|
|
16
|
+
request.result.createObjectStore('files');
|
|
17
|
+
};
|
|
18
|
+
request.onsuccess = () => resolve(request.result);
|
|
19
|
+
request.onerror = () => reject(request.error);
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
async clear(): Promise<void> {
|
|
24
|
+
const db = await this.dbPromise;
|
|
25
|
+
return new Promise((resolve, reject) => {
|
|
26
|
+
const tx = db.transaction('files', 'readwrite');
|
|
27
|
+
const store = tx.objectStore('files');
|
|
28
|
+
const req = store.clear();
|
|
29
|
+
req.onsuccess = () => resolve();
|
|
30
|
+
req.onerror = () => reject(req.error);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getLocalUri(filename: string): string {
|
|
35
|
+
return `indexeddb://${this.databaseName}/files/${filename}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
private async getStore(mode: IDBTransactionMode = 'readonly'): Promise<IDBObjectStore> {
|
|
39
|
+
const db = await this.dbPromise;
|
|
40
|
+
const tx = db.transaction('files', mode);
|
|
41
|
+
return tx.objectStore('files');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async saveFile(filePath: string, data: AttachmentData): Promise<number> {
|
|
45
|
+
const store = await this.getStore('readwrite');
|
|
46
|
+
|
|
47
|
+
let dataToStore: ArrayBuffer;
|
|
48
|
+
let size: number;
|
|
49
|
+
|
|
50
|
+
if (typeof data === 'string') {
|
|
51
|
+
const binaryString = atob(data);
|
|
52
|
+
const bytes = new Uint8Array(binaryString.length);
|
|
53
|
+
for (let i = 0; i < binaryString.length; i++) {
|
|
54
|
+
bytes[i] = binaryString.charCodeAt(i);
|
|
55
|
+
}
|
|
56
|
+
dataToStore = bytes.buffer;
|
|
57
|
+
size = bytes.byteLength;
|
|
58
|
+
} else {
|
|
59
|
+
dataToStore = data;
|
|
60
|
+
size = dataToStore.byteLength;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return await new Promise<number>((resolve, reject) => {
|
|
64
|
+
const req = store.put(dataToStore, filePath);
|
|
65
|
+
req.onsuccess = () => resolve(size);
|
|
66
|
+
req.onerror = () => reject(req.error);
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async readFile(fileUri: string, options?: { encoding?: EncodingType; mediaType?: string }): Promise<ArrayBuffer> {
|
|
71
|
+
const store = await this.getStore();
|
|
72
|
+
return new Promise<ArrayBuffer>((resolve, reject) => {
|
|
73
|
+
const req = store.get(fileUri);
|
|
74
|
+
req.onsuccess = async () => {
|
|
75
|
+
if (!req.result) {
|
|
76
|
+
reject(new Error('File not found'));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
resolve(req.result as ArrayBuffer);
|
|
81
|
+
};
|
|
82
|
+
req.onerror = () => reject(req.error);
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
async deleteFile(uri: string, options?: { filename?: string }): Promise<void> {
|
|
87
|
+
const store = await this.getStore('readwrite');
|
|
88
|
+
await new Promise<void>((resolve, reject) => {
|
|
89
|
+
const req = store.delete(uri);
|
|
90
|
+
req.onsuccess = () => resolve();
|
|
91
|
+
req.onerror = () => reject(req.error);
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async fileExists(fileUri: string): Promise<boolean> {
|
|
96
|
+
const store = await this.getStore();
|
|
97
|
+
return new Promise<boolean>((resolve, reject) => {
|
|
98
|
+
const req = store.get(fileUri);
|
|
99
|
+
req.onsuccess = () => resolve(!!req.result);
|
|
100
|
+
req.onerror = () => reject(req.error);
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async makeDir(path: string): Promise<void> {
|
|
105
|
+
// No-op for IndexedDB as it does not have a directory structure
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
async rmDir(path: string): Promise<void> {
|
|
109
|
+
const store = await this.getStore('readwrite');
|
|
110
|
+
const range = IDBKeyRange.bound(path + '/', path + '/\uffff', false, false);
|
|
111
|
+
await new Promise<void>((resolve, reject) => {
|
|
112
|
+
const req = store.delete(range);
|
|
113
|
+
req.onsuccess = () => resolve();
|
|
114
|
+
req.onerror = () => reject(req.error);
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
}
|
|
@@ -151,13 +151,15 @@ export class LockedAsyncDatabaseAdapter
|
|
|
151
151
|
* Returns a pending operation if one is already in progress.
|
|
152
152
|
*/
|
|
153
153
|
async reOpenInternalDB(): Promise<void> {
|
|
154
|
-
if (!this.options.reOpenOnConnectionClosed) {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
if (this.databaseOpenPromise) {
|
|
154
|
+
if (this.closing || !this.options.reOpenOnConnectionClosed) {
|
|
155
|
+
// No-op
|
|
156
|
+
return;
|
|
157
|
+
} else if (this.databaseOpenPromise) {
|
|
158
|
+
// Already busy opening
|
|
158
159
|
return this.databaseOpenPromise;
|
|
160
|
+
} else {
|
|
161
|
+
return this._reOpen();
|
|
159
162
|
}
|
|
160
|
-
return this._reOpen();
|
|
161
163
|
}
|
|
162
164
|
|
|
163
165
|
protected async _init() {
|
|
@@ -317,9 +319,17 @@ export class LockedAsyncDatabaseAdapter
|
|
|
317
319
|
protected async acquireLock(callback: () => Promise<any>, options?: { timeoutMs?: number }): Promise<any> {
|
|
318
320
|
await this.waitForInitialized();
|
|
319
321
|
|
|
320
|
-
// The database is being opened in the background. Wait for it here.
|
|
322
|
+
// The database is being (re)opened in the background. Wait for it here.
|
|
321
323
|
if (this.databaseOpenPromise) {
|
|
322
324
|
await this.databaseOpenPromise;
|
|
325
|
+
} else if (!this._db) {
|
|
326
|
+
/**
|
|
327
|
+
* The database is not open anymore, we might need to re-open it.
|
|
328
|
+
* Typically, _db, can be `null` if we tried to reOpen the database, but failed to succeed in re-opening.
|
|
329
|
+
* This can happen when disconnecting the client.
|
|
330
|
+
* Note: It is safe to re-enter this method multiple times.
|
|
331
|
+
*/
|
|
332
|
+
await this.reOpenInternalDB();
|
|
323
333
|
}
|
|
324
334
|
|
|
325
335
|
return this._acquireLock(async () => {
|
|
@@ -339,11 +349,9 @@ export class LockedAsyncDatabaseAdapter
|
|
|
339
349
|
return await callback();
|
|
340
350
|
} catch (ex) {
|
|
341
351
|
if (ConnectionClosedError.MATCHES(ex)) {
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
this.reOpenInternalDB();
|
|
346
|
-
}
|
|
352
|
+
// Immediately re-open the database. We need to miss as little table updates as possible.
|
|
353
|
+
// Note, don't await this since it uses the same lock as we're in now.
|
|
354
|
+
this.reOpenInternalDB();
|
|
347
355
|
}
|
|
348
356
|
throw ex;
|
|
349
357
|
} finally {
|
package/src/index.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
export * from '@powersync/common';
|
|
2
|
+
export * from './attachments/IndexDBFileSystemAdapter.js';
|
|
2
3
|
export * from './db/adapters/AbstractWebPowerSyncDatabaseOpenFactory.js';
|
|
3
4
|
export * from './db/adapters/AbstractWebSQLOpenFactory.js';
|
|
4
5
|
export * from './db/adapters/AsyncDatabaseConnection.js';
|