@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/lib/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@powersync/web",
3
- "version": "1.32.0",
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",
@@ -67,7 +67,7 @@
67
67
  "license": "Apache-2.0",
68
68
  "peerDependencies": {
69
69
  "@journeyapps/wa-sqlite": "catalog:",
70
- "@powersync/common": "workspace:^1.46.0"
70
+ "@powersync/common": "workspace:^1.47.0"
71
71
  },
72
72
  "dependencies": {
73
73
  "@powersync/common": "workspace:*",
@@ -0,0 +1,25 @@
1
+ import { AttachmentData, EncodingType, LocalStorageAdapter } from '@powersync/common';
2
+ /**
3
+ * IndexDBFileSystemStorageAdapter implements LocalStorageAdapter using IndexedDB.
4
+ * Suitable for web browsers and web-based environments.
5
+ */
6
+ export declare class IndexDBFileSystemStorageAdapter implements LocalStorageAdapter {
7
+ private databaseName;
8
+ private dbPromise;
9
+ constructor(databaseName?: string);
10
+ initialize(): Promise<void>;
11
+ clear(): Promise<void>;
12
+ getLocalUri(filename: string): string;
13
+ private getStore;
14
+ saveFile(filePath: string, data: AttachmentData): Promise<number>;
15
+ readFile(fileUri: string, options?: {
16
+ encoding?: EncodingType;
17
+ mediaType?: string;
18
+ }): Promise<ArrayBuffer>;
19
+ deleteFile(uri: string, options?: {
20
+ filename?: string;
21
+ }): Promise<void>;
22
+ fileExists(fileUri: string): Promise<boolean>;
23
+ makeDir(path: string): Promise<void>;
24
+ rmDir(path: string): Promise<void>;
25
+ }
@@ -0,0 +1,104 @@
1
+ /**
2
+ * IndexDBFileSystemStorageAdapter implements LocalStorageAdapter using IndexedDB.
3
+ * Suitable for web browsers and web-based environments.
4
+ */
5
+ export class IndexDBFileSystemStorageAdapter {
6
+ databaseName;
7
+ dbPromise;
8
+ constructor(databaseName = 'PowerSyncFiles') {
9
+ this.databaseName = databaseName;
10
+ }
11
+ async initialize() {
12
+ this.dbPromise = new Promise((resolve, reject) => {
13
+ const request = indexedDB.open(this.databaseName, 1);
14
+ request.onupgradeneeded = () => {
15
+ request.result.createObjectStore('files');
16
+ };
17
+ request.onsuccess = () => resolve(request.result);
18
+ request.onerror = () => reject(request.error);
19
+ });
20
+ }
21
+ async clear() {
22
+ const db = await this.dbPromise;
23
+ return new Promise((resolve, reject) => {
24
+ const tx = db.transaction('files', 'readwrite');
25
+ const store = tx.objectStore('files');
26
+ const req = store.clear();
27
+ req.onsuccess = () => resolve();
28
+ req.onerror = () => reject(req.error);
29
+ });
30
+ }
31
+ getLocalUri(filename) {
32
+ return `indexeddb://${this.databaseName}/files/${filename}`;
33
+ }
34
+ async getStore(mode = 'readonly') {
35
+ const db = await this.dbPromise;
36
+ const tx = db.transaction('files', mode);
37
+ return tx.objectStore('files');
38
+ }
39
+ async saveFile(filePath, data) {
40
+ const store = await this.getStore('readwrite');
41
+ let dataToStore;
42
+ let size;
43
+ if (typeof data === 'string') {
44
+ const binaryString = atob(data);
45
+ const bytes = new Uint8Array(binaryString.length);
46
+ for (let i = 0; i < binaryString.length; i++) {
47
+ bytes[i] = binaryString.charCodeAt(i);
48
+ }
49
+ dataToStore = bytes.buffer;
50
+ size = bytes.byteLength;
51
+ }
52
+ else {
53
+ dataToStore = data;
54
+ size = dataToStore.byteLength;
55
+ }
56
+ return await new Promise((resolve, reject) => {
57
+ const req = store.put(dataToStore, filePath);
58
+ req.onsuccess = () => resolve(size);
59
+ req.onerror = () => reject(req.error);
60
+ });
61
+ }
62
+ async readFile(fileUri, options) {
63
+ const store = await this.getStore();
64
+ return new Promise((resolve, reject) => {
65
+ const req = store.get(fileUri);
66
+ req.onsuccess = async () => {
67
+ if (!req.result) {
68
+ reject(new Error('File not found'));
69
+ return;
70
+ }
71
+ resolve(req.result);
72
+ };
73
+ req.onerror = () => reject(req.error);
74
+ });
75
+ }
76
+ async deleteFile(uri, options) {
77
+ const store = await this.getStore('readwrite');
78
+ await new Promise((resolve, reject) => {
79
+ const req = store.delete(uri);
80
+ req.onsuccess = () => resolve();
81
+ req.onerror = () => reject(req.error);
82
+ });
83
+ }
84
+ async fileExists(fileUri) {
85
+ const store = await this.getStore();
86
+ return new Promise((resolve, reject) => {
87
+ const req = store.get(fileUri);
88
+ req.onsuccess = () => resolve(!!req.result);
89
+ req.onerror = () => reject(req.error);
90
+ });
91
+ }
92
+ async makeDir(path) {
93
+ // No-op for IndexedDB as it does not have a directory structure
94
+ }
95
+ async rmDir(path) {
96
+ const store = await this.getStore('readwrite');
97
+ const range = IDBKeyRange.bound(path + '/', path + '/\uffff', false, false);
98
+ await new Promise((resolve, reject) => {
99
+ const req = store.delete(range);
100
+ req.onsuccess = () => resolve();
101
+ req.onerror = () => reject(req.error);
102
+ });
103
+ }
104
+ }
@@ -104,13 +104,17 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
104
104
  * Returns a pending operation if one is already in progress.
105
105
  */
106
106
  async reOpenInternalDB() {
107
- if (!this.options.reOpenOnConnectionClosed) {
108
- throw new Error(`Cannot re-open underlying database, reOpenOnConnectionClosed is not enabled`);
107
+ if (this.closing || !this.options.reOpenOnConnectionClosed) {
108
+ // No-op
109
+ return;
109
110
  }
110
- if (this.databaseOpenPromise) {
111
+ else if (this.databaseOpenPromise) {
112
+ // Already busy opening
111
113
  return this.databaseOpenPromise;
112
114
  }
113
- return this._reOpen();
115
+ else {
116
+ return this._reOpen();
117
+ }
114
118
  }
115
119
  async _init() {
116
120
  /**
@@ -241,10 +245,19 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
241
245
  }
242
246
  async acquireLock(callback, options) {
243
247
  await this.waitForInitialized();
244
- // The database is being opened in the background. Wait for it here.
248
+ // The database is being (re)opened in the background. Wait for it here.
245
249
  if (this.databaseOpenPromise) {
246
250
  await this.databaseOpenPromise;
247
251
  }
252
+ else if (!this._db) {
253
+ /**
254
+ * The database is not open anymore, we might need to re-open it.
255
+ * Typically, _db, can be `null` if we tried to reOpen the database, but failed to succeed in re-opening.
256
+ * This can happen when disconnecting the client.
257
+ * Note: It is safe to re-enter this method multiple times.
258
+ */
259
+ await this.reOpenInternalDB();
260
+ }
248
261
  return this._acquireLock(async () => {
249
262
  let holdId = null;
250
263
  try {
@@ -262,11 +275,9 @@ export class LockedAsyncDatabaseAdapter extends BaseObserver {
262
275
  }
263
276
  catch (ex) {
264
277
  if (ConnectionClosedError.MATCHES(ex)) {
265
- if (this.options.reOpenOnConnectionClosed && !this.databaseOpenPromise && !this.closing) {
266
- // Immediately re-open the database. We need to miss as little table updates as possible.
267
- // Note, don't await this since it uses the same lock as we're in now.
268
- this.reOpenInternalDB();
269
- }
278
+ // Immediately re-open the database. We need to miss as little table updates as possible.
279
+ // Note, don't await this since it uses the same lock as we're in now.
280
+ this.reOpenInternalDB();
270
281
  }
271
282
  throw ex;
272
283
  }
@@ -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';
package/lib/src/index.js 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';